on master help sign in
.cargo/config.tomlnew file+3
since splitsnapshots
unifiedsplit
@@ -0,0 +1,3 @@
1+[alias]
2+xtask = "run --package xtask --"
3+integration-test = "test --features integration --profile integration --workspace --test integration"
.envrcnew file+7
@@ -0,0 +1,7 @@
1+watch_file shell.nix
2+watch_file flake.lock
3+watch_file rust-toolchain.toml
4+
5+# try to use flakes, if it fails use normal nix (ie. shell.nix)
6+use flake || use nix
7+eval "$shellHook"
.gitattributesnew file+11
@@ -0,0 +1,11 @@
1+# Auto detect text files and perform normalization
2+* text=auto
3+
4+*.rs text diff=rust
5+*.toml text diff=toml
6+
7+*.scm text diff=scheme
8+*.md text diff=markdown
9+
10+book/theme/highlight.js linguist-vendored
11+Cargo.lock text
.github/FUNDING.ymlnew file+1
@@ -0,0 +1 @@
1+open_collective: helix-editor
.github/ISSUE_TEMPLATE/blank_issue.mdnew file+4
@@ -0,0 +1,4 @@
1+---
2+name: Blank Issue
3+about: Create a blank issue.
4+---
.github/ISSUE_TEMPLATE/bug_report.yamlnew file+78
@@ -0,0 +1,78 @@
1+name: Bug Report
2+description: Create a report to help us improve
3+labels: C-bug
4+body:
5+ - type: markdown
6+ attributes:
7+ value: Thank you for filing a bug report! 🐛
8+ - type: textarea
9+ id: problem
10+ attributes:
11+ label: Summary
12+ description: >
13+ Please provide a short summary of the bug, along with any information
14+ you feel relevant to replicate the bug.
15+ validations:
16+ required: true
17+ - type: textarea
18+ id: reproduction-steps
19+ attributes:
20+ label: Reproduction Steps
21+ value: |
22+ <!-- Ideally provide a key sequence and/or asciinema.org recording. -->
23+
24+ I tried this:
25+
26+ 1. `hx`
27+
28+ I expected this to happen:
29+
30+ Instead, this happened:
31+ - type: textarea
32+ id: helix-log
33+ attributes:
34+ label: Helix log
35+ description: See `hx -h` for log file path. If you can reproduce the issue run `RUST_BACKTRACE=1 hx -vv` to generate a more detailed log file.
36+ value: |
37+ <details><summary>~/.cache/helix/helix.log</summary>
38+
39+ ```
40+ please provide a copy of `~/.cache/helix/helix.log` here if possible, you may need to redact some of the lines
41+ ```
42+
43+ </details>
44+ - type: input
45+ id: platform
46+ attributes:
47+ label: Platform
48+ placeholder: Linux / macOS / Windows
49+ validations:
50+ required: true
51+ - type: input
52+ id: terminal-emulator
53+ attributes:
54+ label: Terminal Emulator
55+ placeholder: wezterm 20220101-133340-7edc5b5a
56+ validations:
57+ required: true
58+ - type: input
59+ id: installation-method
60+ attributes:
61+ label: Installation Method
62+ description: >
63+ How you installed Helix - from a package manager like Homebrew or the
64+ AUR, built from source, downloaded a binary from the releases page, etc.
65+ placeholder: "source / brew / nixpkgs / flake / releases page"
66+ validations:
67+ required: true
68+ - type: input
69+ id: helix-version
70+ attributes:
71+ label: Helix Version
72+ description: >
73+ Helix version (`hx -V` if using a release, `git describe` if building
74+ from master).
75+ **Make sure that you are using the [latest helix release](https://github.com/helix-editor/helix/releases) or a newer master build**
76+ placeholder: "helix 22.12 (5eaa6d97)"
77+ validations:
78+ required: true
.github/ISSUE_TEMPLATE/enhancement.mdnew file+13
@@ -0,0 +1,13 @@
1+---
2+name: Enhancement
3+about: Suggest an improvement
4+title: ''
5+labels: C-enhancement
6+assignees: ''
7+---
8+
9+<!--
10+Your enhancement may already be reported!
11+Please search on the issue tracker before creating a new issue.
12+If this is an idea for a feature, please open an "Idea" Discussion instead.
13+-->
.github/dependabot.ymlnew file+14
@@ -0,0 +1,14 @@
1+# Please see the documentation for all configuration options:
2+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3+
4+version: 2
5+updates:
6+ - package-ecosystem: "cargo"
7+ directory: "/"
8+ schedule:
9+ interval: "weekly"
10+
11+ - package-ecosystem: "github-actions"
12+ directory: "/"
13+ schedule:
14+ interval: "weekly"
.github/workflows/build.ymlnew file+118
@@ -0,0 +1,118 @@
1+name: Build
2+on:
3+ pull_request:
4+ push:
5+ branches:
6+ - master
7+ merge_group:
8+ schedule:
9+ - cron: '00 01 * * *'
10+
11+jobs:
12+ check:
13+ name: Check (msrv)
14+ runs-on: ubuntu-latest
15+ steps:
16+ - name: Checkout sources
17+ uses: actions/checkout@v4
18+ - name: Install stable toolchain
19+ uses: helix-editor/rust-toolchain@v1
20+ with:
21+ profile: minimal
22+ override: true
23+
24+ - uses: Swatinem/rust-cache@v2
25+ with:
26+ shared-key: "build"
27+
28+ - name: Run cargo check
29+ run: cargo check
30+
31+ test:
32+ name: Test Suite
33+ runs-on: ${{ matrix.os }}
34+ env:
35+ RUST_BACKTRACE: 1
36+ HELIX_LOG_LEVEL: info
37+ steps:
38+ - name: Checkout sources
39+ uses: actions/checkout@v4
40+
41+ - name: Install stable toolchain
42+ uses: dtolnay/rust-toolchain@1.70
43+
44+ - uses: Swatinem/rust-cache@v2
45+ with:
46+ shared-key: "build"
47+
48+ - name: Cache test tree-sitter grammar
49+ uses: actions/cache@v3
50+ with:
51+ path: runtime/grammars
52+ key: ${{ runner.os }}-stable-v${{ env.CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
53+ restore-keys: ${{ runner.os }}-stable-v${{ env.CACHE_VERSION }}-tree-sitter-grammars-
54+
55+ - name: Run cargo test
56+ run: cargo test --workspace
57+
58+ - name: Run cargo integration-test
59+ run: cargo integration-test
60+
61+ strategy:
62+ matrix:
63+ os: [ubuntu-latest, macos-latest, windows-latest]
64+
65+ lints:
66+ name: Lints
67+ runs-on: ubuntu-latest
68+ steps:
69+ - name: Checkout sources
70+ uses: actions/checkout@v4
71+
72+ - name: Install stable toolchain
73+ uses: dtolnay/rust-toolchain@1.70
74+ with:
75+ components: rustfmt, clippy
76+
77+ - uses: Swatinem/rust-cache@v2
78+ with:
79+ shared-key: "build"
80+
81+ - name: Run cargo fmt
82+ run: cargo fmt --all --check
83+
84+ - name: Run cargo clippy
85+ run: cargo clippy --workspace --all-targets -- -D warnings
86+
87+ - name: Run cargo doc
88+ run: cargo doc --no-deps --workspace --document-private-items
89+ env:
90+ RUSTDOCFLAGS: -D warnings
91+
92+ docs:
93+ name: Docs
94+ runs-on: ubuntu-latest
95+ steps:
96+ - name: Checkout sources
97+ uses: actions/checkout@v4
98+
99+ - name: Install stable toolchain
100+ uses: dtolnay/rust-toolchain@1.70
101+
102+ - uses: Swatinem/rust-cache@v2
103+ with:
104+ shared-key: "build"
105+
106+ - name: Validate queries
107+ run: cargo xtask query-check
108+
109+ - name: Generate docs
110+ run: cargo xtask docgen
111+
112+ - name: Check uncommitted documentation changes
113+ run: |
114+ git diff
115+ git diff-files --quiet \
116+ || (echo "Run 'cargo xtask docgen', commit the changes and push again" \
117+ && exit 1)
118+
.github/workflows/cachix.ymlnew file+26
@@ -0,0 +1,26 @@
1+# Publish the Nix flake outputs to Cachix
2+name: Cachix
3+on:
4+ push:
5+ branches:
6+ - master
7+
8+jobs:
9+ publish:
10+ name: Publish Flake
11+ runs-on: ubuntu-latest
12+ steps:
13+ - name: Checkout sources
14+ uses: actions/checkout@v4
15+
16+ - name: Install nix
17+ uses: cachix/install-nix-action@v24
18+
19+ - name: Authenticate with Cachix
20+ uses: cachix/cachix-action@v13
21+ with:
22+ name: helix
23+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
24+
25+ - name: Build nix flake
26+ run: nix build -L
.github/workflows/gh-pages.ymlnew file+41
@@ -0,0 +1,41 @@
1+name: Github Pages
2+
3+on:
4+ push:
5+ branches:
6+ - master
7+ tags:
8+ - '*'
9+
10+jobs:
11+ deploy:
12+ runs-on: ubuntu-latest
13+ steps:
14+ - uses: actions/checkout@v4
15+
16+ - name: Setup mdBook
17+ uses: peaceiris/actions-mdbook@v1
18+ with:
19+ mdbook-version: 'latest'
20+ # mdbook-version: '0.4.8'
21+
22+ - run: mdbook build book
23+
24+ - name: Set output directory
25+ run: |
26+ OUTDIR=$(basename ${{ github.ref }})
27+ echo "OUTDIR=$OUTDIR" >> $GITHUB_ENV
28+
29+ - name: Deploy stable
30+ uses: peaceiris/actions-gh-pages@v3
31+ if: startswith(github.ref, 'refs/tags/')
32+ with:
33+ github_token: ${{ secrets.GITHUB_TOKEN }}
34+ publish_dir: ./book/book
35+
36+ - name: Deploy
37+ uses: peaceiris/actions-gh-pages@v3
38+ with:
39+ github_token: ${{ secrets.GITHUB_TOKEN }}
40+ publish_dir: ./book/book
41+ destination_dir: ./${{ env.OUTDIR }}
.github/workflows/languages.tomlnew file+26
@@ -0,0 +1,26 @@
1+# This languages.toml is used for testing in CI.
2+
3+[[language]]
4+name = "rust"
5+scope = "source.rust"
6+injection-regex = "rust"
7+file-types = ["rs"]
8+comment-token = "//"
9+roots = ["Cargo.toml", "Cargo.lock"]
10+indent = { tab-width = 4, unit = " " }
11+
12+[[grammar]]
13+name = "rust"
14+source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
15+
16+[[language]]
17+name = "nix"
18+scope = "source.nix"
19+injection-regex = "nix"
20+file-types = ["nix"]
21+shebangs = []
22+roots = []
23+comment-token = "#"
24+
25+# A grammar entry is not necessary for this language - it is only used for
26+# testing TOML merging behavior.
.github/workflows/release.ymlnew file+295
@@ -0,0 +1,295 @@
1+name: Release
2+on:
3+ push:
4+ tags:
5+ - '[0-9]+.[0-9]+'
6+ - '[0-9]+.[0-9]+.[0-9]+'
7+ branches:
8+ - 'patch/ci-release-*'
9+ pull_request:
10+ paths:
11+ - '.github/workflows/release.yml'
12+
13+env:
14+ # Preview mode: Publishes the build output as a CI artifact instead of creating
15+ # a release, allowing for manual inspection of the output. This mode is
16+ # activated if the CI run was triggered by events other than pushed tags, or
17+ # if the repository is a fork.
18+ preview: ${{ !startsWith(github.ref, 'refs/tags/') || github.repository != 'helix-editor/helix' }}
19+
20+jobs:
21+ fetch-grammars:
22+ name: Fetch Grammars
23+ runs-on: ubuntu-latest
24+ steps:
25+ - name: Checkout sources
26+ uses: actions/checkout@v4
27+
28+ - name: Install stable toolchain
29+ uses: dtolnay/rust-toolchain@stable
30+
31+ - uses: Swatinem/rust-cache@v2
32+
33+ - name: Fetch tree-sitter grammars
34+ run: cargo run --package=helix-loader --bin=hx-loader
35+
36+ - name: Bundle grammars
37+ run: tar cJf grammars.tar.xz -C runtime/grammars/sources .
38+
39+ - uses: actions/upload-artifact@v4
40+ with:
41+ name: grammars
42+ path: grammars.tar.xz
43+
44+ dist:
45+ name: Dist
46+ needs: [fetch-grammars]
47+ env:
48+ # For some builds, we use cross to test on 32-bit and big-endian
49+ # systems.
50+ CARGO: cargo
51+ # When CARGO is set to CROSS, this is set to `--target matrix.target`.
52+ TARGET_FLAGS:
53+ # When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
54+ TARGET_DIR: ./target
55+ # Emit backtraces on panics.
56+ RUST_BACKTRACE: 1
57+ runs-on: ${{ matrix.os }}
58+ strategy:
59+ fail-fast: false # don't fail other jobs if one fails
60+ matrix:
61+ build: [x86_64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
62+ include:
63+ - build: x86_64-linux
64+ os: ubuntu-latest
65+ rust: stable
66+ target: x86_64-unknown-linux-gnu
67+ cross: false
68+ - build: aarch64-linux
69+ os: ubuntu-latest
70+ rust: stable
71+ target: aarch64-unknown-linux-gnu
72+ cross: true
73+ # - build: riscv64-linux
74+ # os: ubuntu-latest
75+ # rust: stable
76+ # target: riscv64gc-unknown-linux-gnu
77+ # cross: true
78+ - build: x86_64-macos
79+ os: macos-latest
80+ rust: stable
81+ target: x86_64-apple-darwin
82+ cross: false
83+ - build: x86_64-windows
84+ os: windows-latest
85+ rust: stable
86+ target: x86_64-pc-windows-msvc
87+ cross: false
88+ # 23.03: build issues
89+ - build: aarch64-macos
90+ os: macos-latest
91+ rust: stable
92+ target: aarch64-apple-darwin
93+ cross: false
94+ skip_tests: true # x86_64 host can't run aarch64 code
95+ # - build: x86_64-win-gnu
96+ # os: windows-2019
97+ # rust: stable-x86_64-gnu
98+ # target: x86_64-pc-windows-gnu
99+ # - build: win32-msvc
100+ # os: windows-2019
101+ # rust: stable
102+ # target: i686-pc-windows-msvc
103+
104+ steps:
105+ - name: Checkout sources
106+ uses: actions/checkout@v4
107+
108+ - name: Download grammars
109+ uses: actions/download-artifact@v4
110+
111+ - name: Move grammars under runtime
112+ if: "!startsWith(matrix.os, 'windows')"
113+ run: |
114+ mkdir -p runtime/grammars/sources
115+ tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
116+
117+ # The rust-toolchain action ignores rust-toolchain.toml files.
118+ # Removing this before building with cargo ensures that the rust-toolchain
119+ # is considered the same between installation and usage.
120+ - name: Remove the rust-toolchain.toml file
121+ run: rm rust-toolchain.toml
122+
123+ - name: Install ${{ matrix.rust }} toolchain
124+ uses: dtolnay/rust-toolchain@master
125+ with:
126+ toolchain: ${{ matrix.rust }}
127+ target: ${{ matrix.target }}
128+
129+ # Install a pre-release version of Cross
130+ # TODO: We need to pre-install Cross because we need cross-rs/cross#591 to
131+ # get a newer C++ compiler toolchain. Remove this step when Cross
132+ # 0.3.0, which includes cross-rs/cross#591, is released.
133+ - name: Install Cross
134+ if: "matrix.cross"
135+ run: |
136+ cargo install cross --git https://github.com/cross-rs/cross.git --rev 47df5c76e7cba682823a0b6aa6d95c17b31ba63a
137+ echo "CARGO=cross" >> $GITHUB_ENV
138+ # echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
139+ # echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
140+
141+ - name: Show command used for Cargo
142+ run: |
143+ echo "cargo command is: ${{ env.CARGO }}"
144+ echo "target flag is: ${{ env.TARGET_FLAGS }}"
145+
146+ - name: Run cargo test
147+ if: "!matrix.skip_tests"
148+ run: ${{ env.CARGO }} test --release --locked --target ${{ matrix.target }} --workspace
149+
150+ - name: Set profile.release.strip = true
151+ shell: bash
152+ run: |
153+ cat >> .cargo/config.toml <<EOF
154+ [profile.release]
155+ strip = true
156+ EOF
157+
158+ - name: Build release binary
159+ run: ${{ env.CARGO }} build --release --locked --target ${{ matrix.target }}
160+
161+ - name: Build AppImage
162+ shell: bash
163+ if: matrix.build == 'x86_64-linux'
164+ run: |
165+ # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
166+ sudo add-apt-repository universe
167+ sudo apt install libfuse2
168+
169+ mkdir dist
170+
171+ name=dev
172+ if [[ $GITHUB_REF == refs/tags/* ]]; then
173+ name=${GITHUB_REF:10}
174+ fi
175+
176+ build="${{ matrix.build }}"
177+
178+ export VERSION="$name"
179+ export ARCH=${build%-linux}
180+ export APP=helix
181+ export OUTPUT="helix-$VERSION-$ARCH.AppImage"
182+ export UPDATE_INFORMATION="gh-releases-zsync|$GITHUB_REPOSITORY_OWNER|helix|latest|$APP-*-$ARCH.AppImage.zsync"
183+
184+ mkdir -p "$APP.AppDir"/usr/{bin,lib/helix}
185+
186+ cp "target/${{ matrix.target }}/release/hx" "$APP.AppDir/usr/bin/hx"
187+ rm -rf runtime/grammars/sources
188+ cp -r runtime "$APP.AppDir/usr/lib/helix/runtime"
189+
190+ cat << 'EOF' > "$APP.AppDir/AppRun"
191+ #!/bin/sh
192+
193+ APPDIR="$(dirname "$(readlink -f "${0}")")"
194+ HELIX_RUNTIME="$APPDIR/usr/lib/helix/runtime" exec "$APPDIR/usr/bin/hx" "$@"
195+ EOF
196+ chmod 755 "$APP.AppDir/AppRun"
197+
198+ curl -Lo linuxdeploy-x86_64.AppImage \
199+ https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
200+ chmod +x linuxdeploy-x86_64.AppImage
201+
202+ ./linuxdeploy-x86_64.AppImage \
203+ --appdir "$APP.AppDir" -d contrib/Helix.desktop \
204+ -i contrib/helix.png --output appimage
205+
206+ mv "$APP-$VERSION-$ARCH.AppImage" \
207+ "$APP-$VERSION-$ARCH.AppImage.zsync" dist
208+
209+ - name: Build archive
210+ shell: bash
211+ run: |
212+ mkdir -p dist
213+ if [ "${{ matrix.os }}" = "windows-2019" ]; then
214+ cp "target/${{ matrix.target }}/release/hx.exe" "dist/"
215+ else
216+ cp "target/${{ matrix.target }}/release/hx" "dist/"
217+ fi
218+ if [ -d runtime/grammars/sources ]; then
219+ rm -rf runtime/grammars/sources
220+ fi
221+ cp -r runtime dist
222+
223+ - uses: actions/upload-artifact@v4
224+ with:
225+ name: bins-${{ matrix.build }}
226+ path: dist
227+
228+ publish:
229+ name: Publish
230+ needs: [dist]
231+ runs-on: ubuntu-latest
232+ steps:
233+ - name: Checkout sources
234+ uses: actions/checkout@v4
235+
236+ - uses: actions/download-artifact@v4
237+
238+ - name: Build archive
239+ shell: bash
240+ run: |
241+ set -ex
242+
243+ source="$(pwd)"
244+ mkdir -p runtime/grammars/sources
245+ tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
246+ rm -rf grammars
247+
248+ cd "$(mktemp -d)"
249+ mv $source/bins-* .
250+ mkdir dist
251+
252+ for dir in bins-* ; do
253+ platform=${dir#"bins-"}
254+ if [[ $platform =~ "windows" ]]; then
255+ exe=".exe"
256+ fi
257+ pkgname=helix-$GITHUB_REF_NAME-$platform
258+ mkdir -p $pkgname
259+ cp $source/LICENSE $source/README.md $pkgname
260+ mkdir $pkgname/contrib
261+ cp -r $source/contrib/completion $pkgname/contrib
262+ mv bins-$platform/runtime $pkgname/
263+ mv bins-$platform/hx$exe $pkgname
264+ chmod +x $pkgname/hx$exe
265+
266+ if [[ "$platform" = "x86_64-linux" ]]; then
267+ mv bins-$platform/helix-*.AppImage* dist/
268+ fi
269+
270+ if [ "$exe" = "" ]; then
271+ tar cJf dist/$pkgname.tar.xz $pkgname
272+ else
273+ 7z a -r dist/$pkgname.zip $pkgname
274+ fi
275+ done
276+
277+ tar cJf dist/helix-$GITHUB_REF_NAME-source.tar.xz -C $source .
278+ mv dist $source/
279+
280+ - name: Upload binaries to release
281+ uses: svenstaro/upload-release-action@v2
282+ if: env.preview == 'false'
283+ with:
284+ repo_token: ${{ secrets.GITHUB_TOKEN }}
285+ file: dist/*
286+ file_glob: true
287+ tag: ${{ github.ref_name }}
288+ overwrite: true
289+
290+ - name: Upload binaries as artifact
291+ uses: actions/upload-artifact@v4
292+ if: env.preview == 'true'
293+ with:
294+ name: release
295+ path: dist/*
.github/workflows/rust.ymldeleted−135
@@ -1,135 +0,0 @@
1−# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
2−#
3−# While our "example" application has the platform-specific code,
4−# for simplicity we are compiling and testing everything on the Ubuntu environment only.
5−# For multi-OS testing see the `cross.yml` workflow.
6−
7−on: [push, pull_request]
8−
9−name: Build
10−
11−jobs:
12− check:
13− name: Check
14− runs-on: ubuntu-latest
15− steps:
16− - name: Checkout sources
17− uses: actions/checkout@v2
18− with:
19− submodules: true
20−
21− - name: Install stable toolchain
22− uses: actions-rs/toolchain@v1
23− with:
24− profile: minimal
25− toolchain: stable
26− override: true
27−
28− - name: Cache cargo registry
29− uses: actions/cache@v1
30− with:
31− path: ~/.cargo/registry
32− key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
33−
34− - name: Cache cargo index
35− uses: actions/cache@v1
36− with:
37− path: ~/.cargo/git
38− key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
39−
40− - name: Cache cargo target dir
41− uses: actions/cache@v1
42− with:
43− path: target
44− key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
45−
46− - name: Run cargo check
47− uses: actions-rs/cargo@v1
48− with:
49− command: check
50−
51− test:
52− name: Test Suite
53− runs-on: ubuntu-latest
54− steps:
55− - name: Checkout sources
56− uses: actions/checkout@v2
57− with:
58− submodules: true
59−
60− - name: Install stable toolchain
61− uses: actions-rs/toolchain@v1
62− with:
63− profile: minimal
64− toolchain: stable
65− override: true
66−
67− - name: Cache cargo registry
68− uses: actions/cache@v1
69− with:
70− path: ~/.cargo/registry
71− key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
72−
73− - name: Cache cargo index
74− uses: actions/cache@v1
75− with:
76− path: ~/.cargo/git
77− key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
78−
79− - name: Cache cargo target dir
80− uses: actions/cache@v1
81− with:
82− path: target
83− key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
84−
85− - name: Run cargo test
86− uses: actions-rs/cargo@v1
87− with:
88− command: test
89−
90− lints:
91− name: Lints
92− runs-on: ubuntu-latest
93− steps:
94− - name: Checkout sources
95− uses: actions/checkout@v2
96− with:
97− submodules: true
98−
99− - name: Install stable toolchain
100− uses: actions-rs/toolchain@v1
101− with:
102− profile: minimal
103− toolchain: stable
104− override: true
105− components: rustfmt, clippy
106−
107− - name: Cache cargo registry
108− uses: actions/cache@v1
109− with:
110− path: ~/.cargo/registry
111− key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
112−
113− - name: Cache cargo index
114− uses: actions/cache@v1
115− with:
116− path: ~/.cargo/git
117− key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
118−
119− - name: Cache cargo target dir
120− uses: actions/cache@v1
121− with:
122− path: target
123− key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
124−
125− - name: Run cargo fmt
126− uses: actions-rs/cargo@v1
127− with:
128− command: fmt
129− args: --all -- --check
130−
131− - name: Run cargo clippy
132− uses: actions-rs/cargo@v1
133− with:
134− command: clippy
135− args: -- -D warnings
.gitignore+3−1
@@ -1,3 +1,5 @@
1target
1target
2+.direnv
2helix-term/rustfmt.toml
3helix-term/rustfmt.toml
3−helix-syntax/languages/
4+result
5+runtime/grammars
.gitmodulesdeleted−84
@@ -1,84 +0,0 @@
1−[submodule "helix-syntax/languages/tree-sitter-cpp"]
2− path = helix-syntax/languages/tree-sitter-cpp
3− url = https://github.com/tree-sitter/tree-sitter-cpp
4− shallow = true
5−[submodule "helix-syntax/languages/tree-sitter-javascript"]
6− path = helix-syntax/languages/tree-sitter-javascript
7− url = https://github.com/tree-sitter/tree-sitter-javascript
8− shallow = true
9−[submodule "helix-syntax/languages/tree-sitter-julia"]
10− path = helix-syntax/languages/tree-sitter-julia
11− url = https://github.com/tree-sitter/tree-sitter-julia
12− shallow = true
13−[submodule "helix-syntax/languages/tree-sitter-python"]
14− path = helix-syntax/languages/tree-sitter-python
15− url = https://github.com/tree-sitter/tree-sitter-python
16− shallow = true
17−[submodule "helix-syntax/languages/tree-sitter-typescript"]
18− path = helix-syntax/languages/tree-sitter-typescript
19− url = https://github.com/tree-sitter/tree-sitter-typescript
20− shallow = true
21−[submodule "helix-syntax/languages/tree-sitter-agda"]
22− path = helix-syntax/languages/tree-sitter-agda
23− url = https://github.com/tree-sitter/tree-sitter-agda
24− shallow = true
25−[submodule "helix-syntax/languages/tree-sitter-go"]
26− path = helix-syntax/languages/tree-sitter-go
27− url = https://github.com/tree-sitter/tree-sitter-go
28− shallow = true
29−[submodule "helix-syntax/languages/tree-sitter-ruby"]
30− path = helix-syntax/languages/tree-sitter-ruby
31− url = https://github.com/tree-sitter/tree-sitter-ruby
32− shallow = true
33−[submodule "helix-syntax/languages/tree-sitter-java"]
34− path = helix-syntax/languages/tree-sitter-java
35− url = https://github.com/tree-sitter/tree-sitter-java
36− shallow = true
37−[submodule "helix-syntax/languages/tree-sitter-php"]
38− path = helix-syntax/languages/tree-sitter-php
39− url = https://github.com/tree-sitter/tree-sitter-php
40− shallow = true
41−[submodule "helix-syntax/languages/tree-sitter-html"]
42− path = helix-syntax/languages/tree-sitter-html
43− url = https://github.com/tree-sitter/tree-sitter-html
44− shallow = true
45−[submodule "helix-syntax/languages/tree-sitter-scala"]
46− path = helix-syntax/languages/tree-sitter-scala
47− url = https://github.com/tree-sitter/tree-sitter-scala
48− shallow = true
49−[submodule "helix-syntax/languages/tree-sitter-bash"]
50− path = helix-syntax/languages/tree-sitter-bash
51− url = https://github.com/tree-sitter/tree-sitter-bash
52− shallow = true
53−[submodule "helix-syntax/languages/tree-sitter-rust"]
54− path = helix-syntax/languages/tree-sitter-rust
55− url = https://github.com/tree-sitter/tree-sitter-rust
56− shallow = true
57−[submodule "helix-syntax/languages/tree-sitter-json"]
58− path = helix-syntax/languages/tree-sitter-json
59− url = https://github.com/tree-sitter/tree-sitter-json
60− shallow = true
61−[submodule "helix-syntax/languages/tree-sitter-css"]
62− path = helix-syntax/languages/tree-sitter-css
63− url = https://github.com/tree-sitter/tree-sitter-css
64− shallow = true
65−[submodule "helix-syntax/languages/tree-sitter-c-sharp"]
66− path = helix-syntax/languages/tree-sitter-c-sharp
67− url = https://github.com/tree-sitter/tree-sitter-c-sharp
68− shallow = true
69−[submodule "helix-syntax/languages/tree-sitter-c"]
70− path = helix-syntax/languages/tree-sitter-c
71− url = https://github.com/tree-sitter/tree-sitter-c
72− shallow = true
73−[submodule "helix-syntax/languages/tree-sitter-haskell"]
74− path = helix-syntax/languages/tree-sitter-haskell
75− url = https://github.com/tree-sitter/tree-sitter-haskell
76− shallow = true
77−[submodule "helix-syntax/languages/tree-sitter-swift"]
78− path = helix-syntax/languages/tree-sitter-swift
79− url = https://github.com/tree-sitter/tree-sitter-swift
80− shallow = true
81−[submodule "helix-syntax/languages/tree-sitter-toml"]
82− path = helix-syntax/languages/tree-sitter-toml
83− url = https://github.com/ikatyang/tree-sitter-toml
84− shallow = true
.ignorenew file+2
@@ -0,0 +1,2 @@
1+# Things that we don't want ripgrep to search that we do want in git
2+# https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#automatic-filtering
CHANGELOG.mdnew file+1891
@@ -0,0 +1,1891 @@
1+# 23.10 (2023-10-24)
2+
3+A big shout out to all the contributors! We had 118 contributors in this release.
4+
5+Breaking changes:
6+
7+- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507))
8+ - This is a breaking change to language configuration
9+
10+Features:
11+
12+- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507), [#7082](https://github.com/helix-editor/helix/pull/7082), [#7286](https://github.com/helix-editor/helix/pull/7286), [#8374](https://github.com/helix-editor/helix/pull/8374))
13+- Add a statusline element for the selected register ([#7222](https://github.com/helix-editor/helix/pull/7222))
14+- Add `%`, `#`, `.`, `*` and `+` special registers ([#6985](https://github.com/helix-editor/helix/pull/6985))
15+- Add initial support for LSP DidChangeWatchedFiles notifications ([#7665](https://github.com/helix-editor/helix/pull/7665))
16+- Search buffer contents in `global_search` ([#5652](https://github.com/helix-editor/helix/pull/5652))
17+- Add a "smart tab" command that intelligently jumps the cursor on tab ([#4443](https://github.com/helix-editor/helix/pull/4443))
18+- Add a statusline element for whether a file is read-only ([#7222](https://github.com/helix-editor/helix/pull/7222), [#7875](https://github.com/helix-editor/helix/pull/7875))
19+- Syntax highlight regex prompts ([#7738](https://github.com/helix-editor/helix/pull/7738))
20+- Allow defining alignment in indent queries ([#5355](https://github.com/helix-editor/helix/pull/5355))
21+- Show visual feedback in `surround_replace` ([#7588](https://github.com/helix-editor/helix/pull/7588))
22+- Switch to Nucleo for fuzzy matching ([#7814](https://github.com/helix-editor/helix/pull/7814), [#8148](https://github.com/helix-editor/helix/pull/8148), [#8192](https://github.com/helix-editor/helix/pull/8192), [#8194](https://github.com/helix-editor/helix/pull/8194))
23+- Insert a trailing newline on write ([#8157](https://github.com/helix-editor/helix/pull/8157))
24+- Add a `-w`/`--working-dir` CLI flag for specifying a working directory on startup ([#8223](https://github.com/helix-editor/helix/pull/8223), [#8498](https://github.com/helix-editor/helix/pull/8498), [#8520](https://github.com/helix-editor/helix/pull/8520))
25+- Accept a `+N` CLI argument to set the first file's line number ([#8521](https://github.com/helix-editor/helix/pull/8521))
26+- Accept Helix-specific ignore files in `.helix/ignore` and `~/.config/helix/ignore` ([#8099](https://github.com/helix-editor/helix/pull/8099))
27+
28+Commands:
29+
30+- `merge_selections` (`A-minus`) - merge all selections into one selection that covers all ranges ([#7053](https://github.com/helix-editor/helix/pull/7053))
31+- `move_prev_long_word_end` and `extend_prev_long_word_end` - move/extend to the end of the previous WORD ([#6905](https://github.com/helix-editor/helix/pull/6905))
32+- `reverse_selection_contents` - swaps the values of each selection so they are reversed ([#7329](https://github.com/helix-editor/helix/pull/7329))
33+- Add `:rl` and `:rla` aliases for `:reload` and `:reload-all` ([#7158](https://github.com/helix-editor/helix/pull/7158))
34+- `yank_joined` - join the selections and yank to the selected register ([#7195](https://github.com/helix-editor/helix/pull/7195))
35+- `:write-all!` (`:wa!`) - forcibly write all buffers to disk and create any necessary subdirectories ([#7577](https://github.com/helix-editor/helix/pull/7577))
36+- `:redraw` - clear re-render the UI ([#6949](https://github.com/helix-editor/helix/pull/6949))
37+- `:tree-sitter-highlight-name` - show the theme scope name of the highlight under the cursor ([#8170](https://github.com/helix-editor/helix/pull/8170))
38+
39+Usability improvements:
40+
41+- Allow cycling option values at runtime ([#4411](https://github.com/helix-editor/helix/pull/4411), [#7240](https://github.com/helix-editor/helix/pull/7240), [#7877](https://github.com/helix-editor/helix/pull/7877))
42+- Exit gracefully on termination signals ([#7236](https://github.com/helix-editor/helix/pull/7236))
43+- Add plaintext matching fallback to tree-sitter pair matching ([#4288](https://github.com/helix-editor/helix/pull/4288))
44+- Persist register selection in pending keymaps ([0e08349](https://github.com/helix-editor/helix/commit/0e08349))
45+- Propagate the count and register to command palette commands ([b394997](https://github.com/helix-editor/helix/commit/b394997))
46+- Auto indent on `insert_at_line_start` ([#5837](https://github.com/helix-editor/helix/pull/5837))
47+- Add a config option to control whether LSP completions are automatically inserted on preview ([#7189](https://github.com/helix-editor/helix/pull/7189))
48+- Add a config option for default line endings ([#5621](https://github.com/helix-editor/helix/pull/5621), [#7357](https://github.com/helix-editor/helix/pull/7357))
49+- Allow ANSI colors in themes ([#5119](https://github.com/helix-editor/helix/pull/5119))
50+- Match pairs that don't form a standalone tree-sitter node ([#7242](https://github.com/helix-editor/helix/pull/7242))
51+- Allow indent sizes of up to 16 columns ([#7429](https://github.com/helix-editor/helix/pull/7429))
52+- Improve performance of mapping positions through changes ([#7408](https://github.com/helix-editor/helix/pull/7408), [8d39a81](https://github.com/helix-editor/helix/commit/8d39a81), [#7471](https://github.com/helix-editor/helix/pull/7471))
53+- Mark buffers created from stdin as modified ([#7431](https://github.com/helix-editor/helix/pull/7431))
54+- Forcibly shut down uninitialized language servers ([#7449](https://github.com/helix-editor/helix/pull/7449))
55+- Add filename completer for shell prompts ([#7569](https://github.com/helix-editor/helix/pull/7569))
56+- Allow binding F13-F24 ([#7672](https://github.com/helix-editor/helix/pull/7672))
57+- Resolve LSP code actions ([#7677](https://github.com/helix-editor/helix/pull/7677), [#8421](https://github.com/helix-editor/helix/pull/8421))
58+- Save an undo checkpoint before accepting completions ([#7747](https://github.com/helix-editor/helix/pull/7747))
59+- Include gitignored files in debugger completions ([#7936](https://github.com/helix-editor/helix/pull/7936))
60+- Make editor remember the last search register ([#5244](https://github.com/helix-editor/helix/pull/5244))
61+- Open directories with `goto_file` ([#7909](https://github.com/helix-editor/helix/pull/7909))
62+- Use relative path to open buffer in `goto_file` (`gf`) ([#7965](https://github.com/helix-editor/helix/pull/7965))
63+- Support `default` color in themes ([#8083](https://github.com/helix-editor/helix/pull/8083), [#8114](https://github.com/helix-editor/helix/pull/8114))
64+- Toggle between relative and absolute line numbers when the terminal loses focus ([#7955](https://github.com/helix-editor/helix/pull/7955))
65+- Lower default idle-timeout to 250ms ([060e73a](https://github.com/helix-editor/helix/commit/060e73a))
66+- Allow theming diff gutters separately from other diff colors ([#8343](https://github.com/helix-editor/helix/pull/8343))
67+- Style bold/italic/strikethrough in markdown doc popups ([#8385](https://github.com/helix-editor/helix/pull/8385))
68+- Maintain the cursor position and view when splitting with `:hsplit`/`:vsplit` ([#8109](https://github.com/helix-editor/helix/pull/8109))
69+- Accept `-` in macros outside of `<`/`>` ([#8475](https://github.com/helix-editor/helix/pull/8475))
70+- Show all language servers for each language in `--health` ([#7315](https://github.com/helix-editor/helix/pull/7315))
71+- Don't break on hyphens in `:reflow` ([#8569](https://github.com/helix-editor/helix/pull/8569))
72+
73+Fixes:
74+
75+- Update diagnostics correctly on language server exit ([#7111](https://github.com/helix-editor/helix/pull/7111))
76+- Fix off-by-one in `select_references_to_symbol_under_cursor` ([#7132](https://github.com/helix-editor/helix/pull/7132))
77+- Extend selection with repeat-last-motion only if the original motion extended the selection ([#7159](https://github.com/helix-editor/helix/pull/7159))
78+- Fix undefined behavior in the diff gutter ([#7227](https://github.com/helix-editor/helix/pull/7227))
79+- Check that tab width is non-zero ([#7178](https://github.com/helix-editor/helix/pull/7178))
80+- Fix styles being overwritten in table rows with multiple cells ([#7281](https://github.com/helix-editor/helix/pull/7281))
81+- Add file for `--log` CLI arg in help text ([#7307](https://github.com/helix-editor/helix/pull/7307))
82+- Fix underflow when repeating a completion that has a negative shift position ([#7322](https://github.com/helix-editor/helix/pull/7322))
83+- Prefer longer matches in `select_next_sibling` and `select_prev_sibling` ([#7332](https://github.com/helix-editor/helix/pull/7332))
84+- Preview scratch buffers in the jumplist picker ([#7331](https://github.com/helix-editor/helix/pull/7331))
85+- Fix chunking by bytes in tree-sitter parsing ([#7417](https://github.com/helix-editor/helix/pull/7417))
86+- Discard LSP publishDiagnostic from uninitialized servers ([#7467](https://github.com/helix-editor/helix/pull/7467))
87+- Use negotiated position encoding for LSP workspace edits ([#7469](https://github.com/helix-editor/helix/pull/7469))
88+- Fix error message for unknown gutter types in config ([#7534](https://github.com/helix-editor/helix/pull/7534))
89+- Fix `:log-open` when `--log` CLI arg is specified ([#7573](https://github.com/helix-editor/helix/pull/7573), [#7585](https://github.com/helix-editor/helix/pull/7585))
90+- Fix debouncing of LSP messages to fix the last message sticking around ([#7538](https://github.com/helix-editor/helix/pull/7538), [#8023](https://github.com/helix-editor/helix/pull/8023))
91+- Fix crash when the current working directory is deleted ([#7185](https://github.com/helix-editor/helix/pull/7185))
92+- Fix piping to Helix on macOS ([#5468](https://github.com/helix-editor/helix/pull/5468))
93+- Fix crash when parsing overlapping injections ([#7621](https://github.com/helix-editor/helix/pull/7621))
94+- Clear the statusline when the prompt is visible ([#7646](https://github.com/helix-editor/helix/pull/7646))
95+- Fix range formatting error message typo ([#7823](https://github.com/helix-editor/helix/pull/7823))
96+- Skip rendering gutters when gutter width exceeds view width ([#7821](https://github.com/helix-editor/helix/pull/7821))
97+- Center the picker preview using visual lines ([#7837](https://github.com/helix-editor/helix/pull/7837))
98+- Align view correctly for background buffers opened with `A-ret` ([#7691](https://github.com/helix-editor/helix/pull/7691))
99+- Fix cursor resetting to block when quitting via a keybind ([#7931](https://github.com/helix-editor/helix/pull/7931))
100+- Remove path completions for the `:new` command ([#8010](https://github.com/helix-editor/helix/pull/8010))
101+- Use binary path resolved by `which` for formatter commands ([#8064](https://github.com/helix-editor/helix/pull/8064))
102+- Handle crossterm's `hidden` modifier ([#8120](https://github.com/helix-editor/helix/pull/8120))
103+- Clear completion when switching between windows with the mouse ([#8118](https://github.com/helix-editor/helix/pull/8118))
104+- Eagerly remove the last picker (`<space>'`) when the picker has many items ([#8127](https://github.com/helix-editor/helix/pull/8127))
105+- Fix find commands for buffers with non-LF line-endings ([#8111](https://github.com/helix-editor/helix/pull/8111))
106+- Detect the tmux clipboard provider on macOS ([#8182](https://github.com/helix-editor/helix/pull/8182))
107+- Fix syntax highlighting in dynamic picker preview pane ([#8206](https://github.com/helix-editor/helix/pull/8206))
108+- Recognize HTML code tags with attributes as code in markdown previews ([#8397](https://github.com/helix-editor/helix/pull/8397))
109+- Fix multicursor snippet placeholder directions ([#8423](https://github.com/helix-editor/helix/pull/8423))
110+- Only show diagnostic highlights when diagnostics are enabled for a language server ([#8551](https://github.com/helix-editor/helix/pull/8551))
111+
112+Themes:
113+
114+- Improve the selection color in `ferra` ([#7138](https://github.com/helix-editor/helix/pull/7138))
115+- Add `variable.other.member` theming to `spacebones_light` ([#7125](https://github.com/helix-editor/helix/pull/7125))
116+- Update `autumn` and theme the soft-wrap indicator ([#7229](https://github.com/helix-editor/helix/pull/7229))
117+- Add `gruvbox_dark_soft` ([#7139](https://github.com/helix-editor/helix/pull/7139))
118+- Add `merionette` ([#7186](https://github.com/helix-editor/helix/pull/7186))
119+- Add `zed_onedark` and `zed_onelight` ([#7250](https://github.com/helix-editor/helix/pull/7250))
120+- Use light-gray for `onedarker` inlay hint theming ([#7433](https://github.com/helix-editor/helix/pull/7433))
121+- Update the Nord theme to follow the style guidelines ([#7490](https://github.com/helix-editor/helix/pull/7490))
122+- Tune `dark_plus` inlay hint colors ([#7611](https://github.com/helix-editor/helix/pull/7611))
123+- Add `naysayer` ([#7570](https://github.com/helix-editor/helix/pull/7570))
124+- Add `kaolin-dark`, `kaolin-light` and `kaolin-valley-dark` ([#7151](https://github.com/helix-editor/helix/pull/7151))
125+- Fix selection highlighting in gruvbox variants ([#7717](https://github.com/helix-editor/helix/pull/7717))
126+- Add soft-wrap indicator to `gruvbox` ([#7736](https://github.com/helix-editor/helix/pull/7736))
127+- Add missing palette definitions in `everforest_dark` ([#7739](https://github.com/helix-editor/helix/pull/7739))
128+- Increase diagnostics clarity in `pop-dark` ([#7702](https://github.com/helix-editor/helix/pull/7702))
129+- Add `vim_dark_high_contrast` ([#7785](https://github.com/helix-editor/helix/pull/7785))
130+- Add `new_moon` ([#7834](https://github.com/helix-editor/helix/pull/7834))
131+- Add `yellowed` ([#7849](https://github.com/helix-editor/helix/pull/7849))
132+- Improve comment readability for `autumn` ([#7939](https://github.com/helix-editor/helix/pull/7939))
133+- Distinguish active bufferline buffer in `monokai` ([#7983](https://github.com/helix-editor/helix/pull/7983))
134+- Update ruler colors in `nord` ([#7995](https://github.com/helix-editor/helix/pull/7995))
135+- Update Catppuccin themes ([#8102](https://github.com/helix-editor/helix/pull/8102))
136+- Add text focus scope and diagnostics undercurls for `nord` ([#8165](https://github.com/helix-editor/helix/pull/8165))
137+- Add material theme collection ([#8211](https://github.com/helix-editor/helix/pull/8211))
138+- Improve indent line color in `dracula` ([#8266](https://github.com/helix-editor/helix/pull/8266))
139+- Clean up and refactor `papercolor` to use inheritance ([#8276](https://github.com/helix-editor/helix/pull/8276))
140+- Fix `zenburn` inlay hint color ([#8278](https://github.com/helix-editor/helix/pull/8278)a)
141+- Fix picker crash when previewing an invalid range ([e9d0bd7](https://github.com/helix-editor/helix/commit/e9d0bd7))
142+- Correctly center items in the picker preview ([13d4463](https://github.com/helix-editor/helix/commit/13d4463))
143+- Add `cyan_light` ([#8293](https://github.com/helix-editor/helix/pull/8293), [#8587](https://github.com/helix-editor/helix/pull/8587))
144+- Theme HTML tags in `onedark` ([#8409](https://github.com/helix-editor/helix/pull/8409))
145+- Refine `darcula` and `darcula-solid` themes ([#8412](https://github.com/helix-editor/helix/pull/8412))
146+- Improve `nord` highlights ([#8414](https://github.com/helix-editor/helix/pull/8414))
147+- Add `nord-night` ([#8549](https://github.com/helix-editor/helix/pull/8549))
148+
149+New languages:
150+
151+- Blueprint ([#7213](https://github.com/helix-editor/helix/pull/7213), [#8161](https://github.com/helix-editor/helix/pull/8161))
152+- Forth ([#7256](https://github.com/helix-editor/helix/pull/7256), [#7334](https://github.com/helix-editor/helix/pull/7334))
153+- t32 ([#7140](https://github.com/helix-editor/helix/pull/7140), [#7811](https://github.com/helix-editor/helix/pull/7811))
154+- WebC ([#7290](https://github.com/helix-editor/helix/pull/7290))
155+- Persistent DSL for Haskell ([#7261](https://github.com/helix-editor/helix/pull/7261))
156+- F# ([#7619](https://github.com/helix-editor/helix/pull/7619), [#8024](https://github.com/helix-editor/helix/pull/8024))
157+- Wren ([#7765](https://github.com/helix-editor/helix/pull/7765), [#7819](https://github.com/helix-editor/helix/pull/7819))
158+- Unison ([#7724](https://github.com/helix-editor/helix/pull/7724))
159+- Todo.txt ([#7835](https://github.com/helix-editor/helix/pull/7835))
160+- Jinja and Handlebars ([#7233](https://github.com/helix-editor/helix/pull/7233))
161+- Pod ([#7907](https://github.com/helix-editor/helix/pull/7907))
162+- Strace ([#7928](https://github.com/helix-editor/helix/pull/7928))
163+- Gemini ([#8070](https://github.com/helix-editor/helix/pull/8070))
164+- GNU Assembler (GAS) ([#8291](https://github.com/helix-editor/helix/pull/8291))
165+- JSON5 ([#8473](https://github.com/helix-editor/helix/pull/8473))
166+- TEMPL ([#8540](https://github.com/helix-editor/helix/pull/8540))
167+
168+Updated languages and queries:
169+
170+- Add one to the ruler numbers for git-commit ([#7072](https://github.com/helix-editor/helix/pull/7072))
171+- Recognize XAML files as XML ([#7083](https://github.com/helix-editor/helix/pull/7083))
172+- Recognize `Cargo.lock` as TOML ([#7095](https://github.com/helix-editor/helix/pull/7095))
173+- Use Rust grammar for Cairo ([c6d1430](https://github.com/helix-editor/helix/commit/c6d1430))
174+- Update tree-sitter-nickel ([#7059](https://github.com/helix-editor/helix/pull/7059), [#7551](https://github.com/helix-editor/helix/pull/7551))
175+- Tune auto-pair characters for Nickel ([#7059](https://github.com/helix-editor/helix/pull/7059))
176+- Recognize `Vagrantfile` as Ruby ([#7112](https://github.com/helix-editor/helix/pull/7112))
177+- Recognize hidden justfiles as Just ([#7088](https://github.com/helix-editor/helix/pull/7088))
178+- Update Java and TypeScript highlight queries ([#7145](https://github.com/helix-editor/helix/pull/7145))
179+- Recognize `.zimrc` as Bash ([#7146](https://github.com/helix-editor/helix/pull/7146))
180+- Recognize `.gir` as XML ([#7152](https://github.com/helix-editor/helix/pull/7152))
181+- Update tree-sitter-scala ([#7147](https://github.com/helix-editor/helix/pull/7147))
182+- Recognize make file-type as Makefile ([#7212](https://github.com/helix-editor/helix/pull/7212))
183+- Update tree-sitter-verilog ([#7262](https://github.com/helix-editor/helix/pull/7262))
184+- Update tree-sitter-cpp ([#7285](https://github.com/helix-editor/helix/pull/7285))
185+- Support core mode for delve debugger ([#7300](https://github.com/helix-editor/helix/pull/7300))
186+- Add Fortran comment injections ([#7305](https://github.com/helix-editor/helix/pull/7305))
187+- Switch Vue language server to `vue-language-server` ([#7312](https://github.com/helix-editor/helix/pull/7312))
188+- Update tree-sitter-sql ([#7387](https://github.com/helix-editor/helix/pull/7387), [#8464](https://github.com/helix-editor/helix/pull/8464))
189+- Replace the MATLAB tre-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
190+- Highlight TOML table headers ([#7441](https://github.com/helix-editor/helix/pull/7441))
191+- Recognize `cppm` file-type as C++ ([#7492](https://github.com/helix-editor/helix/pull/7492))
192+- Refactor ecma language queries into private and public queries ([#7207](https://github.com/helix-editor/helix/pull/7207))
193+- Update tree-sitter-dart ([#7576](https://github.com/helix-editor/helix/pull/7576))
194+- Add shebang for nushell files ([#7606](https://github.com/helix-editor/helix/pull/7606))
195+- Recognize systemd files as INI ([#7592](https://github.com/helix-editor/helix/pull/7592))
196+- Update TypeScript, TSX and Svelte grammars ([#6874](https://github.com/helix-editor/helix/pull/6874))
197+- Enable inlay hints in the Svelte language server ([#7622](https://github.com/helix-editor/helix/pull/7622))
198+- Recognize `Brewfile`s as Ruby ([#7629](https://github.com/helix-editor/helix/pull/7629))
199+- Add more file-types for R ([#7633](https://github.com/helix-editor/helix/pull/7633))
200+- Switch tree-sitter-perl to official upstream parser ([#7644](https://github.com/helix-editor/helix/pull/7644), [#7947](https://github.com/helix-editor/helix/pull/7947))
201+- Fix predicate typo in comment highlights ([#7732](https://github.com/helix-editor/helix/pull/7732))
202+- Update tree-sitter-prql ([#7771](https://github.com/helix-editor/helix/pull/7771))
203+- Recognize `.gitf` as JSON ([#7781](https://github.com/helix-editor/helix/pull/7781))
204+- Switch V language server to `v-analyzer` ([#7760](https://github.com/helix-editor/helix/pull/7760))
205+- Add protobuf language servers ([#7796](https://github.com/helix-editor/helix/pull/7796))
206+- Update tree-sitter-zig ([#7803](https://github.com/helix-editor/helix/pull/7803))
207+- Update tree-sitter-hare ([#7784](https://github.com/helix-editor/helix/pull/7784))
208+- Add Java indent queries ([#7844](https://github.com/helix-editor/helix/pull/7844))
209+- Update tree-sitter-scheme ([979933b](https://github.com/helix-editor/helix/commit/979933b))
210+- Recognize `scm` as Scheme instead of TSQ ([5707151](https://github.com/helix-editor/helix/commit/5707151))
211+- Update tree-sitter-git-commit ([#7831](https://github.com/helix-editor/helix/pull/7831))
212+- Update JavaScript, TypeScript and TSX grammars ([#7852](https://github.com/helix-editor/helix/pull/7852))
213+- Update tree-sitter-nu ([#7873](https://github.com/helix-editor/helix/pull/7873))
214+- Fix YAML indentation ([#6768](https://github.com/helix-editor/helix/pull/6768))
215+- Add `csharp-ls`, Pyright, Pylyzer and add roots for Python ([#7897](https://github.com/helix-editor/helix/pull/7897), [#8032](https://github.com/helix-editor/helix/pull/8032))
216+- Update tree-sitter-slint ([#7893](https://github.com/helix-editor/helix/pull/7893))
217+- Recognize more ZSH file-types as Bash ([#7930](https://github.com/helix-editor/helix/pull/7930))
218+- Recognize `star` extension as Starlark ([#7922](https://github.com/helix-editor/helix/pull/7922))
219+- Fix inline HTML tag highlighting in markdown ([#7960](https://github.com/helix-editor/helix/pull/7960))
220+- Update tree-sitter-robot ([#7970](https://github.com/helix-editor/helix/pull/7970))
221+- Highlight Dart 3 `sealed` and `base` keywords ([#7974](https://github.com/helix-editor/helix/pull/7974))
222+- Add configuration for `ltex-ls` to the default `languages.toml` ([#7838](https://github.com/helix-editor/helix/pull/7838))
223+- Update tree-sitter-strace ([#8087](https://github.com/helix-editor/helix/pull/8087))
224+- Update tree-sitter-gleam, enable auto-format ([#8085](https://github.com/helix-editor/helix/pull/8085))
225+- Update tree-sitter-esdl ([#8222](https://github.com/helix-editor/helix/pull/8222))
226+- Expand ignore file-types ([#8220](https://github.com/helix-editor/helix/pull/8220))
227+- Recognize feed related formats as XML ([#8232](https://github.com/helix-editor/helix/pull/8232))
228+- Improve YAML injections ([#8217](https://github.com/helix-editor/helix/pull/8217))
229+- Add shebangs for TypeScript, Julia, Java and OCaml ([95e994a](https://github.com/helix-editor/helix/commit/95e994a))
230+- Highlight abbreviations in Scheme ([ef23847](https://github.com/helix-editor/helix/commit/ef23847))
231+- Remove backtic auto-pair in OCaml ([#8260](https://github.com/helix-editor/helix/pull/8260))
232+- Recognize `flake.lock` as JSON ([#8304](https://github.com/helix-editor/helix/pull/8304))
233+- Add Python test script injection for Nix ([b4494e1](https://github.com/helix-editor/helix/commit/b4494e1))
234+- Fix Nix comment injection precedence ([37e48f4](https://github.com/helix-editor/helix/commit/37e48f4))
235+- Recognize editorconfig files as INI ([#8308](https://github.com/helix-editor/helix/pull/8308))
236+- Recognize `.babelrc` as JSON ([#8309](https://github.com/helix-editor/helix/pull/8309))
237+- Switch Purescript to its own tree-sitter parser ([#8306](https://github.com/helix-editor/helix/pull/8306), [#8338](https://github.com/helix-editor/helix/pull/8338), [#8527](https://github.com/helix-editor/helix/pull/8527))
238+- Update Unison highlights ([#8315](https://github.com/helix-editor/helix/pull/8315))
239+- Recognize `.webmanifest` as JSON ([#8342](https://github.com/helix-editor/helix/pull/8342))
240+- Recognize polkit policy files as XML ([#8369](https://github.com/helix-editor/helix/pull/8369))
241+- Recognize polkit rules files as JavaScript ([#8370](https://github.com/helix-editor/helix/pull/8370))
242+- Update Go highlight queries ([#8399](https://github.com/helix-editor/helix/pull/8399))
243+- Add shebangs for Makefiles ([#8410](https://github.com/helix-editor/helix/pull/8410))
244+- Add file-type associations from VSCode ([#8388](https://github.com/helix-editor/helix/pull/8388))
245+- Add validation to JSON/CSS language server configs ([#8433](https://github.com/helix-editor/helix/pull/8433))
246+- Add a configuration for the tailwind language server ([#8442](https://github.com/helix-editor/helix/pull/8442))
247+- Add a configuration for the ansible language server ([#7973](https://github.com/helix-editor/helix/pull/7973))
248+- Add a configuration for the GraphQL language server ([#8492](https://github.com/helix-editor/helix/pull/8492))
249+- Indent while statements in Bash ([#8528](https://github.com/helix-editor/helix/pull/8528))
250+- Update tree-sitter-haskell and queries ([#8558](https://github.com/helix-editor/helix/pull/8558))
251+
252+Packaging:
253+
254+- Add an overlay to the Nix flake ([#7078](https://github.com/helix-editor/helix/pull/7078))
255+- Check for `git` before fetching or building grammars ([#7320](https://github.com/helix-editor/helix/pull/7320))
256+- Refactor Nix flake to use Crane ([#7763](https://github.com/helix-editor/helix/pull/7763))
257+- Remove the aarch64 appimage from the release CI ([#7832](https://github.com/helix-editor/helix/pull/7832))
258+- Add desktop and icon files to Nix flake output ([#7979](https://github.com/helix-editor/helix/pull/7979))
259+- Build flake packages with the latest stable Rust ([#8133](https://github.com/helix-editor/helix/pull/8133))
260+
261+# 23.05 (2023-05-18)
262+
263+23.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
264+
265+Features:
266+
267+- Add a config option to exclude declaration from LSP references request ([#6886](https://github.com/helix-editor/helix/pull/6886))
268+- Enable injecting languages based on their file extension and shebang ([#3970](https://github.com/helix-editor/helix/pull/3970))
269+- Sort the buffer picker by most recent access ([#2980](https://github.com/helix-editor/helix/pull/2980))
270+- Perform syntax highlighting in the picker asynchronously ([#7028](https://github.com/helix-editor/helix/pull/7028))
271+
272+Commands:
273+
274+- `:update` is now aliased as `:u` ([#6835](https://github.com/helix-editor/helix/pull/6835))
275+- Add `extend_to_first_nonwhitespace` which acts the same as `goto_first_nonwhitespace` but always extends ([#6837](https://github.com/helix-editor/helix/pull/6837))
276+- Add `:clear-register` for clearing the given register or all registers ([#5695](https://github.com/helix-editor/helix/pull/5695))
277+- Add `:write-buffer-close` and `:write-buffer-close!` ([#6947](https://github.com/helix-editor/helix/pull/6947))
278+
279+Fixes:
280+
281+- Normalize LSP workspace paths ([#6517](https://github.com/helix-editor/helix/pull/6517))
282+- Robustly handle invalid LSP ranges ([#6512](https://github.com/helix-editor/helix/pull/6512))
283+- Fix line number display for LSP goto pickers ([#6559](https://github.com/helix-editor/helix/pull/6559))
284+- Fix toggling of `soft-wrap.enable` option ([#6656](https://github.com/helix-editor/helix/pull/6656), [58e457a](https://github.com/helix-editor/helix/commit/58e457a), [#6742](https://github.com/helix-editor/helix/pull/6742))
285+- Handle `workspace/configuration` requests from stopped language servers ([#6693](https://github.com/helix-editor/helix/pull/6693))
286+- Fix possible crash from opening the jumplist picker ([#6672](https://github.com/helix-editor/helix/pull/6672))
287+- Fix theme preview returning to current theme on line and word deletions ([#6694](https://github.com/helix-editor/helix/pull/6694))
288+- Re-run crate build scripts on changes to revision and grammar repositories ([#6743](https://github.com/helix-editor/helix/pull/6743))
289+- Fix crash on opening from suspended state ([#6764](https://github.com/helix-editor/helix/pull/6764))
290+- Fix unwrap bug in DAP ([#6786](https://github.com/helix-editor/helix/pull/6786))
291+- Always build tree-sitter parsers with C++14 and C11 ([#6792](https://github.com/helix-editor/helix/pull/6792), [#6834](https://github.com/helix-editor/helix/pull/6834), [#6845](https://github.com/helix-editor/helix/pull/6845))
292+- Exit with a non-zero statuscode when tree-sitter parser builds fail ([#6795](https://github.com/helix-editor/helix/pull/6795))
293+- Flip symbol range in LSP goto commands ([#6794](https://github.com/helix-editor/helix/pull/6794))
294+- Fix runtime toggling of the `mouse` option ([#6675](https://github.com/helix-editor/helix/pull/6675))
295+- Fix panic in inlay hint computation when view anchor is out of bounds ([#6883](https://github.com/helix-editor/helix/pull/6883))
296+- Significantly improve performance of git discovery on slow file systems ([#6890](https://github.com/helix-editor/helix/pull/6890))
297+- Downgrade gix log level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
298+- Conserve BOM and properly support saving UTF16 files ([#6497](https://github.com/helix-editor/helix/pull/6497))
299+- Correctly handle completion re-request ([#6594](https://github.com/helix-editor/helix/pull/6594))
300+- Fix offset encoding in LSP `didChange` notifications ([#6921](https://github.com/helix-editor/helix/pull/6921))
301+- Change `gix` logging level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
302+- Improve error message when writes fail because parent directories do not exist ([#7014](https://github.com/helix-editor/helix/pull/7014))
303+- Replace DAP variables popup instead of pushing more popups ([#7034](https://github.com/helix-editor/helix/pull/7034))
304+- Disable tree-sitter for files after parsing for 500ms ([#7028](https://github.com/helix-editor/helix/pull/7028))
305+- Fix crash when deleting with multiple cursors ([#6024](https://github.com/helix-editor/helix/pull/6024))
306+- Fix selection sliding when deleting forwards in append mode ([#6024](https://github.com/helix-editor/helix/pull/6024))
307+- Fix completion on paths containing spaces ([#6779](https://github.com/helix-editor/helix/pull/6779))
308+
309+Themes:
310+
311+- Style inlay hints in `dracula` theme ([#6515](https://github.com/helix-editor/helix/pull/6515))
312+- Style inlay hints in `onedark` theme ([#6503](https://github.com/helix-editor/helix/pull/6503))
313+- Style inlay hints and the soft-wrap indicator in `varua` ([#6568](https://github.com/helix-editor/helix/pull/6568), [#6589](https://github.com/helix-editor/helix/pull/6589))
314+- Style inlay hints in `emacs` theme ([#6569](https://github.com/helix-editor/helix/pull/6569))
315+- Update `base16_transparent` and `dark_high_contrast` themes ([#6577](https://github.com/helix-editor/helix/pull/6577))
316+- Style inlay hints for `mellow` and `rasmus` themes ([#6583](https://github.com/helix-editor/helix/pull/6583))
317+- Dim pane divider for `base16_transparent` theme ([#6534](https://github.com/helix-editor/helix/pull/6534))
318+- Style inlay hints in `zenburn` theme ([#6593](https://github.com/helix-editor/helix/pull/6593))
319+- Style inlay hints in `boo_berry` theme ([#6625](https://github.com/helix-editor/helix/pull/6625))
320+- Add `ferra` theme ([#6619](https://github.com/helix-editor/helix/pull/6619), [#6776](https://github.com/helix-editor/helix/pull/6776))
321+- Style inlay hints in `nightfox` theme ([#6655](https://github.com/helix-editor/helix/pull/6655))
322+- Fix `ayu` theme family markup code block background ([#6538](https://github.com/helix-editor/helix/pull/6538))
323+- Improve whitespace and search match colors in `rose_pine` theme ([#6679](https://github.com/helix-editor/helix/pull/6679))
324+- Highlight selected items in `base16_transparent` theme ([#6716](https://github.com/helix-editor/helix/pull/6716))
325+- Adjust everforest to resemble original more closely ([#5866](https://github.com/helix-editor/helix/pull/5866))
326+- Refactor `dracula` theme ([#6552](https://github.com/helix-editor/helix/pull/6552), [#6767](https://github.com/helix-editor/helix/pull/6767), [#6855](https://github.com/helix-editor/helix/pull/6855), [#6987](https://github.com/helix-editor/helix/pull/6987))
327+- Style inlay hints in `darcula` theme ([#6732](https://github.com/helix-editor/helix/pull/6732))
328+- Style inlay hints in `kanagawa` theme ([#6773](https://github.com/helix-editor/helix/pull/6773))
329+- Improve `ayu_dark` theme ([#6622](https://github.com/helix-editor/helix/pull/6622))
330+- Refactor `noctis` theme multiple cursor highlighting ([96720e7](https://github.com/helix-editor/helix/commit/96720e7))
331+- Refactor `noctis` theme whitespace rendering and indent guides ([f2ccc03](https://github.com/helix-editor/helix/commit/f2ccc03))
332+- Add `amberwood` theme ([#6924](https://github.com/helix-editor/helix/pull/6924))
333+- Update `nightfox` theme ([#7061](https://github.com/helix-editor/helix/pull/7061))
334+
335+Language support:
336+
337+- R language server: use the `--no-echo` flag to silence output ([#6570](https://github.com/helix-editor/helix/pull/6570))
338+- Recognize CUDA files as C++ ([#6521](https://github.com/helix-editor/helix/pull/6521))
339+- Add support for Hurl ([#6450](https://github.com/helix-editor/helix/pull/6450))
340+- Add textobject queries for Julia ([#6588](https://github.com/helix-editor/helix/pull/6588))
341+- Update Ruby highlight queries ([#6587](https://github.com/helix-editor/helix/pull/6587))
342+- Add xsd to XML file-types ([#6631](https://github.com/helix-editor/helix/pull/6631))
343+- Support Robot Framework ([#6611](https://github.com/helix-editor/helix/pull/6611))
344+- Update Gleam tree-sitter parser ([#6641](https://github.com/helix-editor/helix/pull/6641))
345+- Update git-commit tree-sitter parser ([#6692](https://github.com/helix-editor/helix/pull/6692))
346+- Update Haskell tree-sitter parser ([#6317](https://github.com/helix-editor/helix/pull/6317))
347+- Add injection queries for Haskell quasiquotes ([#6474](https://github.com/helix-editor/helix/pull/6474))
348+- Highlight C/C++ escape sequences ([#6724](https://github.com/helix-editor/helix/pull/6724))
349+- Support Markdoc ([#6432](https://github.com/helix-editor/helix/pull/6432))
350+- Support OpenCL ([#6473](https://github.com/helix-editor/helix/pull/6473))
351+- Support DTD ([#6644](https://github.com/helix-editor/helix/pull/6644))
352+- Fix constant highlighting in Python queries ([#6751](https://github.com/helix-editor/helix/pull/6751))
353+- Support Just ([#6453](https://github.com/helix-editor/helix/pull/6453))
354+- Fix Go locals query for `var_spec` identifiers ([#6763](https://github.com/helix-editor/helix/pull/6763))
355+- Update Markdown tree-sitter parser ([#6785](https://github.com/helix-editor/helix/pull/6785))
356+- Fix Haskell workspace root for cabal projects ([#6828](https://github.com/helix-editor/helix/pull/6828))
357+- Avoid extra indentation in Go switches ([#6817](https://github.com/helix-editor/helix/pull/6817))
358+- Fix Go workspace roots ([#6884](https://github.com/helix-editor/helix/pull/6884))
359+- Set PerlNavigator as the default Perl language server ([#6860](https://github.com/helix-editor/helix/pull/6860))
360+- Highlight more sqlx macros in Rust ([#6793](https://github.com/helix-editor/helix/pull/6793))
361+- Switch Odin tree-sitter grammar ([#6766](https://github.com/helix-editor/helix/pull/6766))
362+- Recognize `poetry.lock` as TOML ([#6928](https://github.com/helix-editor/helix/pull/6928))
363+- Recognize Jupyter notebooks as JSON ([#6927](https://github.com/helix-editor/helix/pull/6927))
364+- Add language server configuration for Crystal ([#6948](https://github.com/helix-editor/helix/pull/6948))
365+- Add `build.gradle.kts` to Java and Scala roots ([#6970](https://github.com/helix-editor/helix/pull/6970))
366+- Recognize `sty` and `cls` files as latex ([#6986](https://github.com/helix-editor/helix/pull/6986))
367+- Update Dockerfile tree-sitter grammar ([#6895](https://github.com/helix-editor/helix/pull/6895))
368+- Add comment injections for Odin ([#7027](https://github.com/helix-editor/helix/pull/7027))
369+- Recognize `gml` as XML ([#7055](https://github.com/helix-editor/helix/pull/7055))
370+- Recognize `geojson` as JSON ([#7054](https://github.com/helix-editor/helix/pull/7054))
371+
372+Packaging:
373+
374+- Update the Nix flake dependencies, remove a deprecated option ([#6546](https://github.com/helix-editor/helix/pull/6546))
375+- Fix and re-enable aarch64-macos release binary builds ([#6504](https://github.com/helix-editor/helix/pull/6504))
376+- The git dependency on `tree-sitter` has been replaced with a regular crates.io dependency ([#6608](https://github.com/helix-editor/helix/pull/6608))
377+
378+# 23.03 (2023-03-31)
379+
380+23.03 brings some long-awaited and exciting features. Thank you to everyone involved! This release saw changes from 102 contributors.
381+
382+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/22.12..23.03).
383+Also check out the [release notes](https://helix-editor.com/news/release-23-03-highlights/) for more commentary on larger features.
384+
385+Breaking changes:
386+
387+- Select diagnostic range in `goto_*_diag` commands ([#4713](https://github.com/helix-editor/helix/pull/4713), [#5164](https://github.com/helix-editor/helix/pull/5164), [#6193](https://github.com/helix-editor/helix/pull/6193))
388+- Remove jump behavior from `increment`/`decrement` ([#4123](https://github.com/helix-editor/helix/pull/4123), [#5929](https://github.com/helix-editor/helix/pull/5929))
389+- Select change range in `goto_*_change` commands ([#5206](https://github.com/helix-editor/helix/pull/5206))
390+- Split file modification indicator from filename statusline elements ([#4731](https://github.com/helix-editor/helix/pull/4731), [#6036](https://github.com/helix-editor/helix/pull/6036))
391+- Jump to symbol ranges in LSP goto commands ([#5986](https://github.com/helix-editor/helix/pull/5986))
392+- Workspace detection now stops at the first `.helix/` directory (merging multiple `.helix/languages.toml` configurations is no longer supported) ([#5748](https://github.com/helix-editor/helix/pull/5748))
393+
394+Features:
395+
396+- Dynamic workspace symbol picker ([#5055](https://github.com/helix-editor/helix/pull/5055))
397+- Soft-wrap ([#5420](https://github.com/helix-editor/helix/pull/5420), [#5786](https://github.com/helix-editor/helix/pull/5786), [#5893](https://github.com/helix-editor/helix/pull/5893), [#6142](https://github.com/helix-editor/helix/pull/6142), [#6440](https://github.com/helix-editor/helix/pull/6440))
398+- Initial support for LSP snippet completions ([#5864](https://github.com/helix-editor/helix/pull/5864), [b1f7528](https://github.com/helix-editor/helix/commit/b1f7528), [#6263](https://github.com/helix-editor/helix/pull/6263), [bbf4800](https://github.com/helix-editor/helix/commit/bbf4800), [90348b8](https://github.com/helix-editor/helix/commit/90348b8), [f87299f](https://github.com/helix-editor/helix/commit/f87299f), [#6371](https://github.com/helix-editor/helix/pull/6371), [9fe3adc](https://github.com/helix-editor/helix/commit/9fe3adc))
399+- Add a statusline element for showing the current version control HEAD ([#5682](https://github.com/helix-editor/helix/pull/5682))
400+- Display LSP type hints ([#5420](https://github.com/helix-editor/helix/pull/5420), [#5934](https://github.com/helix-editor/helix/pull/5934), [#6312](https://github.com/helix-editor/helix/pull/6312))
401+- Enable the Kitty keyboard protocol on terminals with support ([#4939](https://github.com/helix-editor/helix/pull/4939), [#6170](https://github.com/helix-editor/helix/pull/6170), [#6194](https://github.com/helix-editor/helix/pull/6194), [#6438](https://github.com/helix-editor/helix/pull/6438))
402+- Add a statusline element for the basename of the current file ([#5318](https://github.com/helix-editor/helix/pull/5318))
403+- Add substring matching syntax for the picker ([#5658](https://github.com/helix-editor/helix/pull/5658))
404+- Support LSP `textDocument/prepareRename` ([#6103](https://github.com/helix-editor/helix/pull/6103))
405+- Allow multiple runtime directories with priorities ([#5411](https://github.com/helix-editor/helix/pull/5411))
406+- Allow configuring whether to insert or replace completions ([#5728](https://github.com/helix-editor/helix/pull/5728))
407+- Allow per-workspace config file `.helix/config.toml` ([#5748](https://github.com/helix-editor/helix/pull/5748))
408+- Add `workspace-lsp-roots` config option to support multiple LSP roots for use with monorepos ([#5748](https://github.com/helix-editor/helix/pull/5748))
409+
410+Commands:
411+
412+- `:pipe-to` which pipes selections into a shell command and ignores output ([#4931](https://github.com/helix-editor/helix/pull/4931))
413+- `merge_consecutive_selections` (`A-_`) combines all consecutive selections ([#5047](https://github.com/helix-editor/helix/pull/5047))
414+- `rotate_view_reverse` which focuses the previous view ([#5356](https://github.com/helix-editor/helix/pull/5356))
415+- `goto_declaration` (`gD`, requires LSP) which jumps to a symbol's declaration ([#5646](https://github.com/helix-editor/helix/pull/5646))
416+- `file_picker_in_current_buffer_directory` ([#4666](https://github.com/helix-editor/helix/pull/4666))
417+- `:character-info` which shows information about the character under the cursor ([#4000](https://github.com/helix-editor/helix/pull/4000))
418+- `:toggle-option` for toggling config options at runtime ([#4085](https://github.com/helix-editor/helix/pull/4085))
419+- `dap_restart` for restarting a debug session in DAP ([#5651](https://github.com/helix-editor/helix/pull/5651))
420+- `:lsp-stop` to stop the language server of the current buffer ([#5964](https://github.com/helix-editor/helix/pull/5964))
421+- `:reset-diff-change` for resetting a diff hunk to its original text ([#4974](https://github.com/helix-editor/helix/pull/4974))
422+- `:config-open-workspace` for opening the config file local to the current workspace ([#5748](https://github.com/helix-editor/helix/pull/5748))
423+
424+Usability improvements:
425+
426+- Remove empty detail section in completion menu when LSP doesn't send details ([#4902](https://github.com/helix-editor/helix/pull/4902))
427+- Pass client information on LSP initialization ([#4904](https://github.com/helix-editor/helix/pull/4904))
428+- Allow specifying environment variables for language servers in language config ([#4004](https://github.com/helix-editor/helix/pull/4004))
429+- Allow detached git worktrees to be recognized as root paths ([#5097](https://github.com/helix-editor/helix/pull/5097))
430+- Improve error message handling for theme loading failures ([#5073](https://github.com/helix-editor/helix/pull/5073))
431+- Print the names of binaries required for LSP/DAP in health-check ([#5195](https://github.com/helix-editor/helix/pull/5195))
432+- Improve sorting in the picker in cases of ties ([#5169](https://github.com/helix-editor/helix/pull/5169))
433+- Add theming for prompt suggestions ([#5104](https://github.com/helix-editor/helix/pull/5104))
434+- Open a file picker when using `:open` on directories ([#2707](https://github.com/helix-editor/helix/pull/2707), [#5278](https://github.com/helix-editor/helix/pull/5278))
435+- Reload language config with `:config-reload` ([#5239](https://github.com/helix-editor/helix/pull/5239), [#5381](https://github.com/helix-editor/helix/pull/5381), [#5431](https://github.com/helix-editor/helix/pull/5431))
436+- Improve indent queries for python when the tree is errored ([#5332](https://github.com/helix-editor/helix/pull/5332))
437+- Picker: Open files without closing the picker with `A-ret` ([#4435](https://github.com/helix-editor/helix/pull/4435))
438+- Allow theming cursors by primary/secondary and by mode ([#5130](https://github.com/helix-editor/helix/pull/5130))
439+- Allow configuration of the minimum width for the line-numbers gutter ([#4724](https://github.com/helix-editor/helix/pull/4724), [#5696](https://github.com/helix-editor/helix/pull/5696))
440+- Use filename completer for `:run-shell-command` command ([#5729](https://github.com/helix-editor/helix/pull/5729))
441+- Surround with line-endings with `ms<ret>` ([#4571](https://github.com/helix-editor/helix/pull/4571))
442+- Hide duplicate symlinks in file pickers ([#5658](https://github.com/helix-editor/helix/pull/5658))
443+- Tabulate buffer picker contents ([#5777](https://github.com/helix-editor/helix/pull/5777))
444+- Add an option to disable LSP ([#4425](https://github.com/helix-editor/helix/pull/4425))
445+- Short-circuit tree-sitter and word object motions ([#5851](https://github.com/helix-editor/helix/pull/5851))
446+- Add exit code to failed command message ([#5898](https://github.com/helix-editor/helix/pull/5898))
447+- Make `m` textobject look for pairs enclosing selections ([#3344](https://github.com/helix-editor/helix/pull/3344))
448+- Negotiate LSP position encoding ([#5894](https://github.com/helix-editor/helix/pull/5894), [a48d1a4](https://github.com/helix-editor/helix/commit/a48d1a4))
449+- Display deprecated LSP completions with strikethrough ([#5932](https://github.com/helix-editor/helix/pull/5932))
450+- Add JSONRPC request ID to failed LSP/DAP request log messages ([#6010](https://github.com/helix-editor/helix/pull/6010), [#6018](https://github.com/helix-editor/helix/pull/6018))
451+- Ignore case when filtering LSP completions ([#6008](https://github.com/helix-editor/helix/pull/6008))
452+- Show current language when no arguments are passed to `:set-language` ([#5895](https://github.com/helix-editor/helix/pull/5895))
453+- Refactor and rewrite all book documentation ([#5534](https://github.com/helix-editor/helix/pull/5534))
454+- Separate diagnostic picker message and code ([#6095](https://github.com/helix-editor/helix/pull/6095))
455+- Add a config option to bypass undercurl detection ([#6253](https://github.com/helix-editor/helix/pull/6253))
456+- Only complete appropriate arguments for typed commands ([#5966](https://github.com/helix-editor/helix/pull/5966))
457+- Discard outdated LSP diagnostics ([3c9d5d0](https://github.com/helix-editor/helix/commit/3c9d5d0))
458+- Discard outdated LSP workspace edits ([b6a4927](https://github.com/helix-editor/helix/commit/b6a4927))
459+- Run shell commands asynchronously ([#6373](https://github.com/helix-editor/helix/pull/6373))
460+- Show diagnostic codes in LSP diagnostic messages ([#6378](https://github.com/helix-editor/helix/pull/6378))
461+- Highlight the current line in a DAP debug session ([#5957](https://github.com/helix-editor/helix/pull/5957))
462+- Hide signature help if it overlaps with the completion menu ([#5523](https://github.com/helix-editor/helix/pull/5523), [7a69c40](https://github.com/helix-editor/helix/commit/7a69c40))
463+
464+Fixes:
465+
466+- Fix behavior of `auto-completion` flag for completion-on-trigger ([#5042](https://github.com/helix-editor/helix/pull/5042))
467+- Reset editor mode when changing buffers ([#5072](https://github.com/helix-editor/helix/pull/5072))
468+- Respect scrolloff settings in mouse movements ([#5255](https://github.com/helix-editor/helix/pull/5255))
469+- Avoid trailing `s` when only one file is opened ([#5189](https://github.com/helix-editor/helix/pull/5189))
470+- Fix erroneous indent between closers of auto-pairs ([#5330](https://github.com/helix-editor/helix/pull/5330))
471+- Expand `~` when parsing file paths in `:open` ([#5329](https://github.com/helix-editor/helix/pull/5329))
472+- Fix theme inheritance for default themes ([#5218](https://github.com/helix-editor/helix/pull/5218))
473+- Fix `extend_line` with a count when the current line(s) are selected ([#5288](https://github.com/helix-editor/helix/pull/5288))
474+- Prompt: Fix autocompletion for paths containing periods ([#5175](https://github.com/helix-editor/helix/pull/5175))
475+- Skip serializing JSONRPC params if params is null ([#5471](https://github.com/helix-editor/helix/pull/5471))
476+- Fix interaction with the `xclip` clipboard provider ([#5426](https://github.com/helix-editor/helix/pull/5426))
477+- Fix undo/redo execution from the command palette ([#5294](https://github.com/helix-editor/helix/pull/5294))
478+- Fix highlighting of non-block cursors ([#5575](https://github.com/helix-editor/helix/pull/5575))
479+- Fix panic when nooping in `join_selections` and `join_selections_space` ([#5423](https://github.com/helix-editor/helix/pull/5423))
480+- Fix selecting a changed file in global search ([#5639](https://github.com/helix-editor/helix/pull/5639))
481+- Fix initial syntax highlight layer sort order ([#5196](https://github.com/helix-editor/helix/pull/5196))
482+- Fix UTF-8 length handling for shellwords ([#5738](https://github.com/helix-editor/helix/pull/5738))
483+- Remove C-j and C-k bindings from the completion menu ([#5070](https://github.com/helix-editor/helix/pull/5070))
484+- Always commit to history when pasting ([#5790](https://github.com/helix-editor/helix/pull/5790))
485+- Properly handle LSP position encoding ([#5711](https://github.com/helix-editor/helix/pull/5711))
486+- Fix infinite loop in `copy_selection_on_prev_line` ([#5888](https://github.com/helix-editor/helix/pull/5888))
487+- Fix completion popup positioning ([#5842](https://github.com/helix-editor/helix/pull/5842))
488+- Fix a panic when uncommenting a line with only a comment token ([#5933](https://github.com/helix-editor/helix/pull/5933))
489+- Fix panic in `goto_window_center` at EOF ([#5987](https://github.com/helix-editor/helix/pull/5987))
490+- Ignore invalid file URIs sent by a language server ([#6000](https://github.com/helix-editor/helix/pull/6000))
491+- Decode LSP URIs for the workspace diagnostics picker ([#6016](https://github.com/helix-editor/helix/pull/6016))
492+- Fix incorrect usages of `tab_width` with `indent_width` ([#5918](https://github.com/helix-editor/helix/pull/5918))
493+- DAP: Send Disconnect if the Terminated event is received ([#5532](https://github.com/helix-editor/helix/pull/5532))
494+- DAP: Validate key and index exist when requesting variables ([#5628](https://github.com/helix-editor/helix/pull/5628))
495+- Check LSP renaming support before prompting for rename text ([#6257](https://github.com/helix-editor/helix/pull/6257))
496+- Fix indent guide rendering ([#6136](https://github.com/helix-editor/helix/pull/6136))
497+- Fix division by zero panic ([#6155](https://github.com/helix-editor/helix/pull/6155))
498+- Fix lacking space panic ([#6109](https://github.com/helix-editor/helix/pull/6109))
499+- Send error replies for malformed and unhandled LSP requests ([#6058](https://github.com/helix-editor/helix/pull/6058))
500+- Fix table column calculations for dynamic pickers ([#5920](https://github.com/helix-editor/helix/pull/5920))
501+- Skip adding jumplist entries for `:<n>` line number previews ([#5751](https://github.com/helix-editor/helix/pull/5751))
502+- Fix completion race conditions ([#6173](https://github.com/helix-editor/helix/pull/6173))
503+- Fix `shrink_selection` with multiple cursors ([#6093](https://github.com/helix-editor/helix/pull/6093))
504+- Fix indentation calculation for lines with mixed tabs/spaces ([#6278](https://github.com/helix-editor/helix/pull/6278))
505+- No-op `client/registerCapability` LSP requests ([#6258](https://github.com/helix-editor/helix/pull/6258))
506+- Send the STOP signal to all processes in the process group ([#3546](https://github.com/helix-editor/helix/pull/3546))
507+- Fix workspace edit client capabilities declaration ([7bf168d](https://github.com/helix-editor/helix/commit/7bf168d))
508+- Fix highlighting in picker results with multiple columns ([#6333](https://github.com/helix-editor/helix/pull/6333))
509+- Canonicalize paths before stripping the current dir as a prefix ([#6290](https://github.com/helix-editor/helix/pull/6290))
510+- Fix truncation behavior for long path names in the file picker ([#6410](https://github.com/helix-editor/helix/pull/6410), [67783dd](https://github.com/helix-editor/helix/commit/67783dd))
511+- Fix theme reloading behavior in `:config-reload` ([ab819d8](https://github.com/helix-editor/helix/commit/ab819d8))
512+
513+Themes:
514+
515+- Update `serika` ([#5038](https://github.com/helix-editor/helix/pull/5038), [#6344](https://github.com/helix-editor/helix/pull/6344))
516+- Update `flatwhite` ([#5036](https://github.com/helix-editor/helix/pull/5036), [#6323](https://github.com/helix-editor/helix/pull/6323))
517+- Update `autumn` ([#5051](https://github.com/helix-editor/helix/pull/5051), [#5397](https://github.com/helix-editor/helix/pull/5397), [#6280](https://github.com/helix-editor/helix/pull/6280), [#6316](https://github.com/helix-editor/helix/pull/6316))
518+- Update `acme` ([#5019](https://github.com/helix-editor/helix/pull/5019), [#5486](https://github.com/helix-editor/helix/pull/5486), [#5488](https://github.com/helix-editor/helix/pull/5488))
519+- Update `gruvbox` themes ([#5066](https://github.com/helix-editor/helix/pull/5066), [#5333](https://github.com/helix-editor/helix/pull/5333), [#5540](https://github.com/helix-editor/helix/pull/5540), [#6285](https://github.com/helix-editor/helix/pull/6285), [#6295](https://github.com/helix-editor/helix/pull/6295))
520+- Update `base16_transparent` ([#5105](https://github.com/helix-editor/helix/pull/5105))
521+- Update `dark_high_contrast` ([#5105](https://github.com/helix-editor/helix/pull/5105))
522+- Update `dracula` ([#5236](https://github.com/helix-editor/helix/pull/5236), [#5627](https://github.com/helix-editor/helix/pull/5627), [#6414](https://github.com/helix-editor/helix/pull/6414))
523+- Update `monokai_pro_spectrum` ([#5250](https://github.com/helix-editor/helix/pull/5250), [#5602](https://github.com/helix-editor/helix/pull/5602))
524+- Update `rose_pine` ([#5267](https://github.com/helix-editor/helix/pull/5267), [#5489](https://github.com/helix-editor/helix/pull/5489), [#6384](https://github.com/helix-editor/helix/pull/6384))
525+- Update `kanagawa` ([#5273](https://github.com/helix-editor/helix/pull/5273), [#5571](https://github.com/helix-editor/helix/pull/5571), [#6085](https://github.com/helix-editor/helix/pull/6085))
526+- Update `emacs` ([#5334](https://github.com/helix-editor/helix/pull/5334))
527+- Add `github` themes ([#5353](https://github.com/helix-editor/helix/pull/5353), [efeec12](https://github.com/helix-editor/helix/commit/efeec12))
528+ - Dark themes: `github_dark`, `github_dark_colorblind`, `github_dark_dimmed`, `github_dark_high_contrast`, `github_dark_tritanopia`
529+ - Light themes: `github_light`, `github_light_colorblind`, `github_light_dimmed`, `github_light_high_contrast`, `github_light_tritanopia`
530+- Update `solarized` variants ([#5445](https://github.com/helix-editor/helix/pull/5445), [#6327](https://github.com/helix-editor/helix/pull/6327))
531+- Update `catppuccin` variants ([#5404](https://github.com/helix-editor/helix/pull/5404), [#6107](https://github.com/helix-editor/helix/pull/6107), [#6269](https://github.com/helix-editor/helix/pull/6269), [#6464](https://github.com/helix-editor/helix/pull/6464))
532+- Use curly underlines in built-in themes ([#5419](https://github.com/helix-editor/helix/pull/5419))
533+- Update `zenburn` ([#5573](https://github.com/helix-editor/helix/pull/5573))
534+- Rewrite `snazzy` ([#3971](https://github.com/helix-editor/helix/pull/3971))
535+- Add `monokai_aqua` ([#5578](https://github.com/helix-editor/helix/pull/5578))
536+- Add `markup.strikethrough` to existing themes ([#5619](https://github.com/helix-editor/helix/pull/5619))
537+- Update `sonokai` ([#5440](https://github.com/helix-editor/helix/pull/5440))
538+- Update `onedark` ([#5755](https://github.com/helix-editor/helix/pull/5755))
539+- Add `ayu_evolve` ([#5638](https://github.com/helix-editor/helix/pull/5638), [#6028](https://github.com/helix-editor/helix/pull/6028), [#6225](https://github.com/helix-editor/helix/pull/6225))
540+- Add `jellybeans` ([#5719](https://github.com/helix-editor/helix/pull/5719))
541+- Update `fleet_dark` ([#5605](https://github.com/helix-editor/helix/pull/5605), [#6266](https://github.com/helix-editor/helix/pull/6266), [#6324](https://github.com/helix-editor/helix/pull/6324), [#6375](https://github.com/helix-editor/helix/pull/6375))
542+- Add `darcula-solid` ([#5778](https://github.com/helix-editor/helix/pull/5778))
543+- Remove text background from monokai themes ([#6009](https://github.com/helix-editor/helix/pull/6009))
544+- Update `pop_dark` ([#5992](https://github.com/helix-editor/helix/pull/5992), [#6208](https://github.com/helix-editor/helix/pull/6208), [#6227](https://github.com/helix-editor/helix/pull/6227), [#6292](https://github.com/helix-editor/helix/pull/6292))
545+- Add `everblush` ([#6086](https://github.com/helix-editor/helix/pull/6086))
546+- Add `adwaita-dark` ([#6042](https://github.com/helix-editor/helix/pull/6042), [#6342](https://github.com/helix-editor/helix/pull/6342))
547+- Update `papercolor` ([#6162](https://github.com/helix-editor/helix/pull/6162))
548+- Update `onelight` ([#6192](https://github.com/helix-editor/helix/pull/6192), [#6276](https://github.com/helix-editor/helix/pull/6276))
549+- Add `molokai` ([#6260](https://github.com/helix-editor/helix/pull/6260))
550+- Update `ayu` variants ([#6329](https://github.com/helix-editor/helix/pull/6329))
551+- Update `tokyonight` variants ([#6349](https://github.com/helix-editor/helix/pull/6349))
552+- Update `nord` variants ([#6376](https://github.com/helix-editor/helix/pull/6376))
553+
554+New languages:
555+
556+- BibTeX ([#5064](https://github.com/helix-editor/helix/pull/5064))
557+- Mermaid.js ([#5147](https://github.com/helix-editor/helix/pull/5147))
558+- Crystal ([#4993](https://github.com/helix-editor/helix/pull/4993), [#5205](https://github.com/helix-editor/helix/pull/5205))
559+- MATLAB/Octave ([#5192](https://github.com/helix-editor/helix/pull/5192))
560+- `tfvars` (uses HCL) ([#5396](https://github.com/helix-editor/helix/pull/5396))
561+- Ponylang ([#5416](https://github.com/helix-editor/helix/pull/5416))
562+- DHall ([1f6809c](https://github.com/helix-editor/helix/commit/1f6809c))
563+- Sagemath ([#5649](https://github.com/helix-editor/helix/pull/5649))
564+- MSBuild ([#5793](https://github.com/helix-editor/helix/pull/5793))
565+- pem ([#5797](https://github.com/helix-editor/helix/pull/5797))
566+- passwd ([#4959](https://github.com/helix-editor/helix/pull/4959))
567+- hosts ([#4950](https://github.com/helix-editor/helix/pull/4950), [#5914](https://github.com/helix-editor/helix/pull/5914))
568+- uxntal ([#6047](https://github.com/helix-editor/helix/pull/6047))
569+- Yuck ([#6064](https://github.com/helix-editor/helix/pull/6064), [#6242](https://github.com/helix-editor/helix/pull/6242))
570+- GNU gettext PO ([#5996](https://github.com/helix-editor/helix/pull/5996))
571+- Sway ([#6023](https://github.com/helix-editor/helix/pull/6023))
572+- NASM ([#6068](https://github.com/helix-editor/helix/pull/6068))
573+- PRQL ([#6126](https://github.com/helix-editor/helix/pull/6126))
574+- reStructuredText ([#6180](https://github.com/helix-editor/helix/pull/6180))
575+- Smithy ([#6370](https://github.com/helix-editor/helix/pull/6370))
576+- VHDL ([#5826](https://github.com/helix-editor/helix/pull/5826))
577+- Rego (OpenPolicy Agent) ([#6415](https://github.com/helix-editor/helix/pull/6415))
578+- Nim ([#6123](https://github.com/helix-editor/helix/pull/6123))
579+
580+Updated languages and queries:
581+
582+- Use diff syntax for patch files ([#5085](https://github.com/helix-editor/helix/pull/5085))
583+- Add Haskell textobjects ([#5061](https://github.com/helix-editor/helix/pull/5061))
584+- Fix commonlisp configuration ([#5091](https://github.com/helix-editor/helix/pull/5091))
585+- Update Scheme ([bae890d](https://github.com/helix-editor/helix/commit/bae890d))
586+- Add indent queries for Bash ([#5149](https://github.com/helix-editor/helix/pull/5149))
587+- Recognize `c++` as a C++ extension ([#5183](https://github.com/helix-editor/helix/pull/5183))
588+- Enable HTTP server in `metals` (Scala) config ([#5551](https://github.com/helix-editor/helix/pull/5551))
589+- Change V-lang language server to `v ls` from `vls` ([#5677](https://github.com/helix-editor/helix/pull/5677))
590+- Inject comment grammar into Nix ([#5208](https://github.com/helix-editor/helix/pull/5208))
591+- Update Rust highlights ([#5238](https://github.com/helix-editor/helix/pull/5238), [#5349](https://github.com/helix-editor/helix/pull/5349))
592+- Fix HTML injection within Markdown ([#5265](https://github.com/helix-editor/helix/pull/5265))
593+- Fix comment token for godot ([#5276](https://github.com/helix-editor/helix/pull/5276))
594+- Expand injections for Vue ([#5268](https://github.com/helix-editor/helix/pull/5268))
595+- Add `.bash_aliases` as a Bash file-type ([#5347](https://github.com/helix-editor/helix/pull/5347))
596+- Fix comment token for sshclientconfig ([#5351](https://github.com/helix-editor/helix/pull/5351))
597+- Update Prisma ([#5417](https://github.com/helix-editor/helix/pull/5417))
598+- Update C++ ([#5457](https://github.com/helix-editor/helix/pull/5457))
599+- Add more file-types for Python ([#5593](https://github.com/helix-editor/helix/pull/5593))
600+- Update tree-sitter-scala ([#5576](https://github.com/helix-editor/helix/pull/5576))
601+- Add an injection regex for Lua ([#5606](https://github.com/helix-editor/helix/pull/5606))
602+- Add `build.gradle` to java roots configuration ([#5641](https://github.com/helix-editor/helix/pull/5641))
603+- Add Hub PR files to markdown file-types ([#5634](https://github.com/helix-editor/helix/pull/5634))
604+- Add an external formatter configuration for Cue ([#5679](https://github.com/helix-editor/helix/pull/5679))
605+- Add injections for builders and writers to Nix ([#5629](https://github.com/helix-editor/helix/pull/5629))
606+- Update tree-sitter-xml to fix whitespace parsing ([#5685](https://github.com/helix-editor/helix/pull/5685))
607+- Add `Justfile` to the make file-types configuration ([#5687](https://github.com/helix-editor/helix/pull/5687))
608+- Update tree-sitter-sql and highlight queries ([#5683](https://github.com/helix-editor/helix/pull/5683), [#5772](https://github.com/helix-editor/helix/pull/5772))
609+- Use the bash grammar and queries for env language ([#5720](https://github.com/helix-editor/helix/pull/5720))
610+- Add podspec files to ruby file-types ([#5811](https://github.com/helix-editor/helix/pull/5811))
611+- Recognize `.C` and `.H` file-types as C++ ([#5808](https://github.com/helix-editor/helix/pull/5808))
612+- Recognize plist and mobileconfig files as XML ([#5863](https://github.com/helix-editor/helix/pull/5863))
613+- Fix `select` indentation in Go ([#5713](https://github.com/helix-editor/helix/pull/5713))
614+- Check for external file modifications when writing ([#5805](https://github.com/helix-editor/helix/pull/5805))
615+- Recognize containerfiles as dockerfile syntax ([#5873](https://github.com/helix-editor/helix/pull/5873))
616+- Update godot grammar and queries ([#5944](https://github.com/helix-editor/helix/pull/5944), [#6186](https://github.com/helix-editor/helix/pull/6186))
617+- Improve DHall highlights ([#5959](https://github.com/helix-editor/helix/pull/5959))
618+- Recognize `.env.dist` and `source.env` as env language ([#6003](https://github.com/helix-editor/helix/pull/6003))
619+- Update tree-sitter-git-rebase ([#6030](https://github.com/helix-editor/helix/pull/6030), [#6094](https://github.com/helix-editor/helix/pull/6094))
620+- Improve SQL highlights ([#6041](https://github.com/helix-editor/helix/pull/6041))
621+- Improve markdown highlights and inject LaTeX ([#6100](https://github.com/helix-editor/helix/pull/6100))
622+- Add textobject queries for Elm ([#6084](https://github.com/helix-editor/helix/pull/6084))
623+- Recognize graphql schema file type ([#6159](https://github.com/helix-editor/helix/pull/6159))
624+- Improve highlighting in comments ([#6143](https://github.com/helix-editor/helix/pull/6143))
625+- Improve highlighting for JavaScript/TypeScript/ECMAScript languages ([#6205](https://github.com/helix-editor/helix/pull/6205))
626+- Improve PHP highlights ([#6203](https://github.com/helix-editor/helix/pull/6203), [#6250](https://github.com/helix-editor/helix/pull/6250), [#6299](https://github.com/helix-editor/helix/pull/6299))
627+- Improve Go highlights ([#6204](https://github.com/helix-editor/helix/pull/6204))
628+- Highlight unchecked sqlx functions as SQL in Rust ([#6256](https://github.com/helix-editor/helix/pull/6256))
629+- Improve Erlang highlights ([cdd6c8d](https://github.com/helix-editor/helix/commit/cdd6c8d))
630+- Improve Nix highlights ([fb4d703](https://github.com/helix-editor/helix/commit/fb4d703))
631+- Improve gdscript highlights ([#6311](https://github.com/helix-editor/helix/pull/6311))
632+- Improve Vlang highlights ([#6279](https://github.com/helix-editor/helix/pull/6279))
633+- Improve Makefile highlights ([#6339](https://github.com/helix-editor/helix/pull/6339))
634+- Remove auto-pair for `'` in OCaml ([#6381](https://github.com/helix-editor/helix/pull/6381))
635+- Fix indents in switch statements in ECMA languages ([#6369](https://github.com/helix-editor/helix/pull/6369))
636+- Recognize xlb and storyboard file-types as XML ([#6407](https://github.com/helix-editor/helix/pull/6407))
637+- Recognize cts and mts file-types as TypeScript ([#6424](https://github.com/helix-editor/helix/pull/6424))
638+- Recognize SVG file-type as XML ([#6431](https://github.com/helix-editor/helix/pull/6431))
639+- Add theme scopes for (un)checked list item markup scopes ([#6434](https://github.com/helix-editor/helix/pull/6434))
640+- Update git commit grammar and add the comment textobject ([#6439](https://github.com/helix-editor/helix/pull/6439), [#6493](https://github.com/helix-editor/helix/pull/6493))
641+- Recognize ARB file-type as JSON ([#6452](https://github.com/helix-editor/helix/pull/6452))
642+- Inject markdown into markdown strings in Julia ([#6489](https://github.com/helix-editor/helix/pull/6489))
643+
644+Packaging:
645+
646+- Fix Nix flake devShell for darwin hosts ([#5368](https://github.com/helix-editor/helix/pull/5368))
647+- Add Appstream metadata file to `contrib/` ([#5643](https://github.com/helix-editor/helix/pull/5643))
648+- Increase the MSRV to 1.65 ([#5570](https://github.com/helix-editor/helix/pull/5570), [#6185](https://github.com/helix-editor/helix/pull/6185))
649+- Expose the Nix flake's `wrapper` ([#5994](https://github.com/helix-editor/helix/pull/5994))
650+
651+# 22.12 (2022-12-06)
652+
653+This is a great big release filled with changes from a 99 contributors. A big _thank you_ to you all!
654+
655+As usual, the following is a summary of each of the changes since the last release.
656+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/22.08.1..22.12).
657+
658+Breaking changes:
659+
660+- Remove readline-like navigation bindings from the default insert mode keymap ([e12690e](https://github.com/helix-editor/helix/commit/e12690e), [#3811](https://github.com/helix-editor/helix/pull/3811), [#3827](https://github.com/helix-editor/helix/pull/3827), [#3915](https://github.com/helix-editor/helix/pull/3915), [#4088](https://github.com/helix-editor/helix/pull/4088))
661+- Rename `append_to_line` as `insert_at_line_end` and `prepend_to_line` as `insert_at_line_start` ([#3753](https://github.com/helix-editor/helix/pull/3753))
662+- Swap diagnostic picker and debug mode bindings in the space keymap ([#4229](https://github.com/helix-editor/helix/pull/4229))
663+- Select newly inserted text on paste or from shell commands ([#4458](https://github.com/helix-editor/helix/pull/4458), [#4608](https://github.com/helix-editor/helix/pull/4608), [#4619](https://github.com/helix-editor/helix/pull/4619), [#4824](https://github.com/helix-editor/helix/pull/4824))
664+- Select newly inserted surrounding characters on `ms<char>` ([#4752](https://github.com/helix-editor/helix/pull/4752))
665+- Exit select-mode after executing `replace_*` commands ([#4554](https://github.com/helix-editor/helix/pull/4554))
666+- Exit select-mode after executing surround commands ([#4858](https://github.com/helix-editor/helix/pull/4858))
667+- Change tree-sitter text-object keys ([#3782](https://github.com/helix-editor/helix/pull/3782))
668+- Rename `fleetish` theme to `fleet_dark` ([#4997](https://github.com/helix-editor/helix/pull/4997))
669+
670+Features:
671+
672+- Bufferline ([#2759](https://github.com/helix-editor/helix/pull/2759))
673+- Support underline styles and colors ([#4061](https://github.com/helix-editor/helix/pull/4061), [98c121c](https://github.com/helix-editor/helix/commit/98c121c))
674+- Inheritance for themes ([#3067](https://github.com/helix-editor/helix/pull/3067), [#4096](https://github.com/helix-editor/helix/pull/4096))
675+- Cursorcolumn ([#4084](https://github.com/helix-editor/helix/pull/4084))
676+- Overhauled system for writing files and quiting ([#2267](https://github.com/helix-editor/helix/pull/2267), [#4397](https://github.com/helix-editor/helix/pull/4397))
677+- Autosave when terminal loses focus ([#3178](https://github.com/helix-editor/helix/pull/3178))
678+- Use OSC52 as a fallback for the system clipboard ([#3220](https://github.com/helix-editor/helix/pull/3220))
679+- Show git diffs in the gutter ([#3890](https://github.com/helix-editor/helix/pull/3890), [#5012](https://github.com/helix-editor/helix/pull/5012), [#4995](https://github.com/helix-editor/helix/pull/4995))
680+- Add a logo ([dc1ec56](https://github.com/helix-editor/helix/commit/dc1ec56))
681+- Multi-cursor completion ([#4496](https://github.com/helix-editor/helix/pull/4496))
682+
683+Commands:
684+
685+- `file_picker_in_current_directory` (`<space>F`) ([#3701](https://github.com/helix-editor/helix/pull/3701))
686+- `:lsp-restart` to restart the current document's language server ([#3435](https://github.com/helix-editor/helix/pull/3435), [#3972](https://github.com/helix-editor/helix/pull/3972))
687+- `join_selections_space` (`A-j`) which joins selections and selects the joining whitespace ([#3549](https://github.com/helix-editor/helix/pull/3549))
688+- `:update` to write the current file if it is modified ([#4426](https://github.com/helix-editor/helix/pull/4426))
689+- `:lsp-workspace-command` for picking LSP commands to execute ([#3140](https://github.com/helix-editor/helix/pull/3140))
690+- `extend_prev_word_end` - the extend variant for `move_prev_word_end` ([7468fa2](https://github.com/helix-editor/helix/commit/7468fa2))
691+- `make_search_word_bounded` which adds regex word boundaries to the current search register value ([#4322](https://github.com/helix-editor/helix/pull/4322))
692+- `:reload-all` - `:reload` for all open buffers ([#4663](https://github.com/helix-editor/helix/pull/4663), [#4901](https://github.com/helix-editor/helix/pull/4901))
693+- `goto_next_change` (`]g`), `goto_prev_change` (`[g`), `goto_first_change` (`[G`), `goto_last_change` (`]G`) textobjects for jumping between VCS changes ([#4650](https://github.com/helix-editor/helix/pull/4650))
694+
695+Usability improvements and fixes:
696+
697+- Don't log 'LSP not defined' errors in the logfile ([1caba2d](https://github.com/helix-editor/helix/commit/1caba2d))
698+- Look for the external formatter program before invoking it ([#3670](https://github.com/helix-editor/helix/pull/3670))
699+- Don't send LSP didOpen events for documents without URLs ([44b4479](https://github.com/helix-editor/helix/commit/44b4479))
700+- Fix off-by-one in `extend_line_above` command ([#3689](https://github.com/helix-editor/helix/pull/3689))
701+- Use the original scroll offset when opening a split ([1acdfaa](https://github.com/helix-editor/helix/commit/1acdfaa))
702+- Handle auto-formatting failures and save the file anyway ([#3684](https://github.com/helix-editor/helix/pull/3684))
703+- Ensure the cursor is in view after `:reflow` ([#3733](https://github.com/helix-editor/helix/pull/3733))
704+- Add default rulers and reflow config for git commit messages ([#3738](https://github.com/helix-editor/helix/pull/3738))
705+- Improve grammar fetching and building output ([#3773](https://github.com/helix-editor/helix/pull/3773))
706+- Add a `text` language to language completion ([cc47d3f](https://github.com/helix-editor/helix/commit/cc47d3f))
707+- Improve error handling for `:set-language` ([e8add6f](https://github.com/helix-editor/helix/commit/e8add6f))
708+- Improve error handling for `:config-reload` ([#3668](https://github.com/helix-editor/helix/pull/3668))
709+- Improve error handling when passing improper ranges to syntax highlighting ([#3826](https://github.com/helix-editor/helix/pull/3826))
710+- Render `<code>` tags as raw markup in markdown ([#3425](https://github.com/helix-editor/helix/pull/3425))
711+- Remove border around the LSP code-actions popup ([#3444](https://github.com/helix-editor/helix/pull/3444))
712+- Canonicalize the path to the runtime directory ([#3794](https://github.com/helix-editor/helix/pull/3794))
713+- Add a `themelint` xtask for linting themes ([#3234](https://github.com/helix-editor/helix/pull/3234))
714+- Re-sort LSP diagnostics after applying transactions ([#3895](https://github.com/helix-editor/helix/pull/3895), [#4319](https://github.com/helix-editor/helix/pull/4319))
715+- Add a command-line flag to specify the log file ([#3807](https://github.com/helix-editor/helix/pull/3807))
716+- Track source and tag information in LSP diagnostics ([#3898](https://github.com/helix-editor/helix/pull/3898), [1df32c9](https://github.com/helix-editor/helix/commit/1df32c9))
717+- Fix theme returning to normal when exiting the `:theme` completion ([#3644](https://github.com/helix-editor/helix/pull/3644))
718+- Improve error messages for invalid commands in the keymap ([#3931](https://github.com/helix-editor/helix/pull/3931))
719+- Deduplicate regexs in `search_selection` command ([#3941](https://github.com/helix-editor/helix/pull/3941))
720+- Split the finding of LSP root and config roots ([#3929](https://github.com/helix-editor/helix/pull/3929))
721+- Ensure that the cursor is within view after auto-formatting ([#4047](https://github.com/helix-editor/helix/pull/4047))
722+- Add pseudo-pending to commands with on-next-key callbacks ([#4062](https://github.com/helix-editor/helix/pull/4062), [#4077](https://github.com/helix-editor/helix/pull/4077))
723+- Add live preview to `:goto` ([#2982](https://github.com/helix-editor/helix/pull/2982))
724+- Show regex compilation failure in a popup ([#3049](https://github.com/helix-editor/helix/pull/3049))
725+- Add 'cycled to end' and 'no more matches' for search ([#3176](https://github.com/helix-editor/helix/pull/3176), [#4101](https://github.com/helix-editor/helix/pull/4101))
726+- Add extending behavior to tree-sitter textobjects ([#3266](https://github.com/helix-editor/helix/pull/3266))
727+- Add `ui.gutter.selected` option for themes ([#3303](https://github.com/helix-editor/helix/pull/3303))
728+- Make statusline mode names configurable ([#3311](https://github.com/helix-editor/helix/pull/3311))
729+- Add a statusline element for total line count ([#3960](https://github.com/helix-editor/helix/pull/3960))
730+- Add extending behavior to `goto_window_*` commands ([#3985](https://github.com/helix-editor/helix/pull/3985))
731+- Fix a panic in signature help when the preview is too large ([#4030](https://github.com/helix-editor/helix/pull/4030))
732+- Add command names to the command palette ([#4071](https://github.com/helix-editor/helix/pull/4071), [#4223](https://github.com/helix-editor/helix/pull/4223), [#4495](https://github.com/helix-editor/helix/pull/4495))
733+- Find the LSP workspace root from the current document's path ([#3553](https://github.com/helix-editor/helix/pull/3553))
734+- Add an option to skip indent-guide levels ([#3819](https://github.com/helix-editor/helix/pull/3819), [2c36e33](https://github.com/helix-editor/helix/commit/2c36e33))
735+- Change focus to modified docs on quit ([#3872](https://github.com/helix-editor/helix/pull/3872))
736+- Respond to `USR1` signal by reloading config ([#3952](https://github.com/helix-editor/helix/pull/3952))
737+- Exit gracefully when the close operation fails ([#4081](https://github.com/helix-editor/helix/pull/4081))
738+- Fix goto/view center mismatch ([#4135](https://github.com/helix-editor/helix/pull/4135))
739+- Highlight the current file picker document on idle-timeout ([#3172](https://github.com/helix-editor/helix/pull/3172), [a85e386](https://github.com/helix-editor/helix/commit/a85e386))
740+- Apply transactions to jumplist selections ([#4186](https://github.com/helix-editor/helix/pull/4186), [#4227](https://github.com/helix-editor/helix/pull/4227), [#4733](https://github.com/helix-editor/helix/pull/4733), [#4865](https://github.com/helix-editor/helix/pull/4865), [#4912](https://github.com/helix-editor/helix/pull/4912), [#4965](https://github.com/helix-editor/helix/pull/4965), [#4981](https://github.com/helix-editor/helix/pull/4981))
741+- Use space as a separator for fuzzy matcher ([#3969](https://github.com/helix-editor/helix/pull/3969))
742+- Overlay all diagnostics with highest severity on top ([#4113](https://github.com/helix-editor/helix/pull/4113))
743+- Avoid re-parsing unmodified tree-sitter injections ([#4146](https://github.com/helix-editor/helix/pull/4146))
744+- Add extending captures for indentation, re-enable python indentation ([#3382](https://github.com/helix-editor/helix/pull/3382), [3e84434](https://github.com/helix-editor/helix/commit/3e84434))
745+- Only allow either `--vsplit` or `--hsplit` CLI flags at once ([#4202](https://github.com/helix-editor/helix/pull/4202))
746+- Fix append cursor location when selection anchor is at the end of the document ([#4147](https://github.com/helix-editor/helix/pull/4147))
747+- Improve selection yanking message ([#4275](https://github.com/helix-editor/helix/pull/4275))
748+- Log failures to load tree-sitter grammars as errors ([#4315](https://github.com/helix-editor/helix/pull/4315))
749+- Fix rendering of lines longer than 65,536 columns ([#4172](https://github.com/helix-editor/helix/pull/4172))
750+- Skip searching `.git` in `global_search` ([#4334](https://github.com/helix-editor/helix/pull/4334))
751+- Display tree-sitter scopes in a popup ([#4337](https://github.com/helix-editor/helix/pull/4337))
752+- Fix deleting a word from the end of the buffer ([#4328](https://github.com/helix-editor/helix/pull/4328))
753+- Pretty print the syntax tree in `:tree-sitter-subtree` ([#4295](https://github.com/helix-editor/helix/pull/4295), [#4606](https://github.com/helix-editor/helix/pull/4606))
754+- Allow specifying suffixes for file-type detection ([#2455](https://github.com/helix-editor/helix/pull/2455), [#4414](https://github.com/helix-editor/helix/pull/4414))
755+- Fix multi-byte auto-pairs ([#4024](https://github.com/helix-editor/helix/pull/4024))
756+- Improve sort scoring for LSP code-actions and completions ([#4134](https://github.com/helix-editor/helix/pull/4134))
757+- Fix the handling of quotes within shellwords ([#4098](https://github.com/helix-editor/helix/pull/4098))
758+- Fix `delete_word_backward` and `delete_word_forward` on newlines ([#4392](https://github.com/helix-editor/helix/pull/4392))
759+- Fix 'no entry found for key' crash on `:write-all` ([#4384](https://github.com/helix-editor/helix/pull/4384))
760+- Remove lowercase requirement for tree-sitter grammars ([#4346](https://github.com/helix-editor/helix/pull/4346))
761+- Resolve LSP completion items on idle-timeout ([#4406](https://github.com/helix-editor/helix/pull/4406), [#4797](https://github.com/helix-editor/helix/pull/4797))
762+- Render diagnostics in the file picker preview ([#4324](https://github.com/helix-editor/helix/pull/4324))
763+- Fix terminal freezing on `shell_insert_output` ([#4156](https://github.com/helix-editor/helix/pull/4156))
764+- Allow use of the count in the repeat operator (`.`) ([#4450](https://github.com/helix-editor/helix/pull/4450))
765+- Show the current theme name on `:theme` with no arguments ([#3740](https://github.com/helix-editor/helix/pull/3740))
766+- Fix rendering in very large terminals ([#4318](https://github.com/helix-editor/helix/pull/4318))
767+- Sort LSP preselected items to the top of the completion menu ([#4480](https://github.com/helix-editor/helix/pull/4480))
768+- Trim braces and quotes from paths in goto-file ([#4370](https://github.com/helix-editor/helix/pull/4370))
769+- Prevent automatic signature help outside of insert mode ([#4456](https://github.com/helix-editor/helix/pull/4456))
770+- Fix freezes with external programs that process stdin and stdout concurrently ([#4180](https://github.com/helix-editor/helix/pull/4180))
771+- Make `scroll` aware of tabs and wide characters ([#4519](https://github.com/helix-editor/helix/pull/4519))
772+- Correctly handle escaping in `command_mode` completion ([#4316](https://github.com/helix-editor/helix/pull/4316), [#4587](https://github.com/helix-editor/helix/pull/4587), [#4632](https://github.com/helix-editor/helix/pull/4632))
773+- Fix `delete_char_backward` for paired characters ([#4558](https://github.com/helix-editor/helix/pull/4558))
774+- Fix crash from two windows editing the same document ([#4570](https://github.com/helix-editor/helix/pull/4570))
775+- Fix pasting from the blackhole register ([#4497](https://github.com/helix-editor/helix/pull/4497))
776+- Support LSP insertReplace completion items ([1312682](https://github.com/helix-editor/helix/commit/1312682))
777+- Dynamically resize the line number gutter width ([#3469](https://github.com/helix-editor/helix/pull/3469))
778+- Fix crash for unknown completion item kinds ([#4658](https://github.com/helix-editor/helix/pull/4658))
779+- Re-enable `format_selections` for single selection ranges ([d4f5cab](https://github.com/helix-editor/helix/commit/d4f5cab))
780+- Limit the number of in-progress tree-sitter query matches ([#4707](https://github.com/helix-editor/helix/pull/4707), [#4830](https://github.com/helix-editor/helix/pull/4830))
781+- Use the special `#` register with `increment`/`decrement` to change by range number ([#4418](https://github.com/helix-editor/helix/pull/4418))
782+- Add a statusline element to show number of selected chars ([#4682](https://github.com/helix-editor/helix/pull/4682))
783+- Add a statusline element showing global LSP diagnostic warning and error counts ([#4569](https://github.com/helix-editor/helix/pull/4569))
784+- Add a scrollbar to popups ([#4449](https://github.com/helix-editor/helix/pull/4449))
785+- Prefer shorter matches in fuzzy matcher scoring ([#4698](https://github.com/helix-editor/helix/pull/4698))
786+- Use key-sequence format for command palette keybinds ([#4712](https://github.com/helix-editor/helix/pull/4712))
787+- Remove prefix filtering from autocompletion menu ([#4578](https://github.com/helix-editor/helix/pull/4578))
788+- Focus on the parent buffer when closing a split ([#4766](https://github.com/helix-editor/helix/pull/4766))
789+- Handle language server termination ([#4797](https://github.com/helix-editor/helix/pull/4797), [#4852](https://github.com/helix-editor/helix/pull/4852))
790+- Allow `r`/`t`/`f` to work on tab characters ([#4817](https://github.com/helix-editor/helix/pull/4817))
791+- Show a preview for scratch buffers in the buffer picker ([#3454](https://github.com/helix-editor/helix/pull/3454))
792+- Set a limit of entries in the jumplist ([#4750](https://github.com/helix-editor/helix/pull/4750))
793+- Re-use shell outputs when inserting or appending shell output ([#3465](https://github.com/helix-editor/helix/pull/3465))
794+- Check LSP server provider capabilities ([#3554](https://github.com/helix-editor/helix/pull/3554))
795+- Improve tree-sitter parsing performance on files with many language layers ([#4716](https://github.com/helix-editor/helix/pull/4716))
796+- Move indentation to the next line when using `<ret>` on a line with only whitespace ([#4854](https://github.com/helix-editor/helix/pull/4854))
797+- Remove selections for closed views from all documents ([#4888](https://github.com/helix-editor/helix/pull/4888))
798+- Improve performance of the `:reload` command ([#4457](https://github.com/helix-editor/helix/pull/4457))
799+- Properly handle media keys ([#4887](https://github.com/helix-editor/helix/pull/4887))
800+- Support LSP diagnostic data field ([#4935](https://github.com/helix-editor/helix/pull/4935))
801+- Handle C-i keycode as tab ([#4961](https://github.com/helix-editor/helix/pull/4961))
802+- Fix view alignment for jumplist picker jumps ([#3743](https://github.com/helix-editor/helix/pull/3743))
803+- Use OSC52 for tmux clipboard provider ([#5027](https://github.com/helix-editor/helix/pull/5027))
804+
805+Themes:
806+
807+- Add `varua` ([#3610](https://github.com/helix-editor/helix/pull/3610), [#4964](https://github.com/helix-editor/helix/pull/4964))
808+- Update `boo_berry` ([#3653](https://github.com/helix-editor/helix/pull/3653))
809+- Add `rasmus` ([#3728](https://github.com/helix-editor/helix/pull/3728))
810+- Add `papercolor_dark` ([#3742](https://github.com/helix-editor/helix/pull/3742))
811+- Update `monokai_pro_spectrum` ([#3814](https://github.com/helix-editor/helix/pull/3814))
812+- Update `nord` ([#3792](https://github.com/helix-editor/helix/pull/3792))
813+- Update `fleetish` ([#3844](https://github.com/helix-editor/helix/pull/3844), [#4487](https://github.com/helix-editor/helix/pull/4487), [#4813](https://github.com/helix-editor/helix/pull/4813))
814+- Update `flatwhite` ([#3843](https://github.com/helix-editor/helix/pull/3843))
815+- Add `darcula` ([#3739](https://github.com/helix-editor/helix/pull/3739))
816+- Update `papercolor` ([#3938](https://github.com/helix-editor/helix/pull/3938), [#4317](https://github.com/helix-editor/helix/pull/4317))
817+- Add bufferline colors to multiple themes ([#3881](https://github.com/helix-editor/helix/pull/3881))
818+- Add `gruvbox_dark_hard` ([#3948](https://github.com/helix-editor/helix/pull/3948))
819+- Add `onedarker` ([#3980](https://github.com/helix-editor/helix/pull/3980), [#4060](https://github.com/helix-editor/helix/pull/4060))
820+- Add `dark_high_contrast` ([#3312](https://github.com/helix-editor/helix/pull/3312))
821+- Update `bogster` ([#4121](https://github.com/helix-editor/helix/pull/4121), [#4264](https://github.com/helix-editor/helix/pull/4264))
822+- Update `sonokai` ([#4089](https://github.com/helix-editor/helix/pull/4089))
823+- Update `ayu_*` themes ([#4140](https://github.com/helix-editor/helix/pull/4140), [#4109](https://github.com/helix-editor/helix/pull/4109), [#4662](https://github.com/helix-editor/helix/pull/4662), [#4764](https://github.com/helix-editor/helix/pull/4764))
824+- Update `everforest` ([#3998](https://github.com/helix-editor/helix/pull/3998))
825+- Update `monokai_pro_octagon` ([#4247](https://github.com/helix-editor/helix/pull/4247))
826+- Add `heisenberg` ([#4209](https://github.com/helix-editor/helix/pull/4209))
827+- Add `bogster_light` ([#4265](https://github.com/helix-editor/helix/pull/4265))
828+- Update `pop-dark` ([#4323](https://github.com/helix-editor/helix/pull/4323))
829+- Update `rose_pine` ([#4221](https://github.com/helix-editor/helix/pull/4221))
830+- Add `kanagawa` ([#4300](https://github.com/helix-editor/helix/pull/4300))
831+- Add `hex_steel`, `hex_toxic` and `hex_lavendar` ([#4367](https://github.com/helix-editor/helix/pull/4367), [#4990](https://github.com/helix-editor/helix/pull/4990))
832+- Update `tokyonight` and `tokyonight_storm` ([#4415](https://github.com/helix-editor/helix/pull/4415))
833+- Update `gruvbox` ([#4626](https://github.com/helix-editor/helix/pull/4626))
834+- Update `dark_plus` ([#4661](https://github.com/helix-editor/helix/pull/4661), [#4678](https://github.com/helix-editor/helix/pull/4678))
835+- Add `zenburn` ([#4613](https://github.com/helix-editor/helix/pull/4613), [#4977](https://github.com/helix-editor/helix/pull/4977))
836+- Update `monokai_pro` ([#4789](https://github.com/helix-editor/helix/pull/4789))
837+- Add `mellow` ([#4770](https://github.com/helix-editor/helix/pull/4770))
838+- Add `nightfox` ([#4769](https://github.com/helix-editor/helix/pull/4769), [#4966](https://github.com/helix-editor/helix/pull/4966))
839+- Update `doom_acario_dark` ([#4979](https://github.com/helix-editor/helix/pull/4979))
840+- Update `autumn` ([#4996](https://github.com/helix-editor/helix/pull/4996))
841+- Update `acme` ([#4999](https://github.com/helix-editor/helix/pull/4999))
842+- Update `nord_light` ([#4999](https://github.com/helix-editor/helix/pull/4999))
843+- Update `serika_*` ([#5015](https://github.com/helix-editor/helix/pull/5015))
844+
845+LSP configurations:
846+
847+- Switch to `openscad-lsp` for OpenScad ([#3750](https://github.com/helix-editor/helix/pull/3750))
848+- Support Jsonnet ([#3748](https://github.com/helix-editor/helix/pull/3748))
849+- Support Markdown ([#3499](https://github.com/helix-editor/helix/pull/3499))
850+- Support Bass ([#3771](https://github.com/helix-editor/helix/pull/3771))
851+- Set roots configuration for Elixir and HEEx ([#3917](https://github.com/helix-editor/helix/pull/3917), [#3959](https://github.com/helix-editor/helix/pull/3959))
852+- Support Purescript ([#4242](https://github.com/helix-editor/helix/pull/4242))
853+- Set roots configuration for Julia ([#4361](https://github.com/helix-editor/helix/pull/4361))
854+- Support D ([#4372](https://github.com/helix-editor/helix/pull/4372))
855+- Increase default language server timeout for Julia ([#4575](https://github.com/helix-editor/helix/pull/4575))
856+- Use ElixirLS for HEEx ([#4679](https://github.com/helix-editor/helix/pull/4679))
857+- Support Bicep ([#4403](https://github.com/helix-editor/helix/pull/4403))
858+- Switch to `nil` for Nix ([433ccef](https://github.com/helix-editor/helix/commit/433ccef))
859+- Support QML ([#4842](https://github.com/helix-editor/helix/pull/4842))
860+- Enable auto-format for CSS ([#4987](https://github.com/helix-editor/helix/pull/4987))
861+- Support CommonLisp ([4176769](https://github.com/helix-editor/helix/commit/4176769))
862+
863+New languages:
864+
865+- SML ([#3692](https://github.com/helix-editor/helix/pull/3692))
866+- Jsonnet ([#3714](https://github.com/helix-editor/helix/pull/3714))
867+- Godot resource ([#3759](https://github.com/helix-editor/helix/pull/3759))
868+- Astro ([#3829](https://github.com/helix-editor/helix/pull/3829))
869+- SSH config ([#2455](https://github.com/helix-editor/helix/pull/2455), [#4538](https://github.com/helix-editor/helix/pull/4538))
870+- Bass ([#3771](https://github.com/helix-editor/helix/pull/3771))
871+- WAT (WebAssembly text format) ([#4040](https://github.com/helix-editor/helix/pull/4040), [#4542](https://github.com/helix-editor/helix/pull/4542))
872+- Purescript ([#4242](https://github.com/helix-editor/helix/pull/4242))
873+- D ([#4372](https://github.com/helix-editor/helix/pull/4372), [#4562](https://github.com/helix-editor/helix/pull/4562))
874+- VHS ([#4486](https://github.com/helix-editor/helix/pull/4486))
875+- KDL ([#4481](https://github.com/helix-editor/helix/pull/4481))
876+- XML ([#4518](https://github.com/helix-editor/helix/pull/4518))
877+- WIT ([#4525](https://github.com/helix-editor/helix/pull/4525))
878+- ENV ([#4536](https://github.com/helix-editor/helix/pull/4536))
879+- INI ([#4538](https://github.com/helix-editor/helix/pull/4538))
880+- Bicep ([#4403](https://github.com/helix-editor/helix/pull/4403), [#4751](https://github.com/helix-editor/helix/pull/4751))
881+- QML ([#4842](https://github.com/helix-editor/helix/pull/4842))
882+- CommonLisp ([4176769](https://github.com/helix-editor/helix/commit/4176769))
883+
884+Updated languages and queries:
885+
886+- Zig ([#3621](https://github.com/helix-editor/helix/pull/3621), [#4745](https://github.com/helix-editor/helix/pull/4745))
887+- Rust ([#3647](https://github.com/helix-editor/helix/pull/3647), [#3729](https://github.com/helix-editor/helix/pull/3729), [#3927](https://github.com/helix-editor/helix/pull/3927), [#4073](https://github.com/helix-editor/helix/pull/4073), [#4510](https://github.com/helix-editor/helix/pull/4510), [#4659](https://github.com/helix-editor/helix/pull/4659), [#4717](https://github.com/helix-editor/helix/pull/4717))
888+- Solidity ([20ed8c2](https://github.com/helix-editor/helix/commit/20ed8c2))
889+- Fish ([#3704](https://github.com/helix-editor/helix/pull/3704))
890+- Elixir ([#3645](https://github.com/helix-editor/helix/pull/3645), [#4333](https://github.com/helix-editor/helix/pull/4333), [#4821](https://github.com/helix-editor/helix/pull/4821))
891+- Diff ([#3708](https://github.com/helix-editor/helix/pull/3708))
892+- Nix ([665e27f](https://github.com/helix-editor/helix/commit/665e27f), [1fe3273](https://github.com/helix-editor/helix/commit/1fe3273))
893+- Markdown ([#3749](https://github.com/helix-editor/helix/pull/3749), [#4078](https://github.com/helix-editor/helix/pull/4078), [#4483](https://github.com/helix-editor/helix/pull/4483), [#4478](https://github.com/helix-editor/helix/pull/4478))
894+- GDScript ([#3760](https://github.com/helix-editor/helix/pull/3760))
895+- JSX and TSX ([#3853](https://github.com/helix-editor/helix/pull/3853), [#3973](https://github.com/helix-editor/helix/pull/3973))
896+- Ruby ([#3976](https://github.com/helix-editor/helix/pull/3976), [#4601](https://github.com/helix-editor/helix/pull/4601))
897+- R ([#4031](https://github.com/helix-editor/helix/pull/4031))
898+- WGSL ([#3996](https://github.com/helix-editor/helix/pull/3996), [#4079](https://github.com/helix-editor/helix/pull/4079))
899+- C# ([#4118](https://github.com/helix-editor/helix/pull/4118), [#4281](https://github.com/helix-editor/helix/pull/4281), [#4213](https://github.com/helix-editor/helix/pull/4213))
900+- Twig ([#4176](https://github.com/helix-editor/helix/pull/4176))
901+- Lua ([#3552](https://github.com/helix-editor/helix/pull/3552))
902+- C/C++ ([#4079](https://github.com/helix-editor/helix/pull/4079), [#4278](https://github.com/helix-editor/helix/pull/4278), [#4282](https://github.com/helix-editor/helix/pull/4282))
903+- Cairo ([17488f1](https://github.com/helix-editor/helix/commit/17488f1), [431f9c1](https://github.com/helix-editor/helix/commit/431f9c1), [09a6df1](https://github.com/helix-editor/helix/commit/09a6df1))
904+- Rescript ([#4356](https://github.com/helix-editor/helix/pull/4356))
905+- Zig ([#4409](https://github.com/helix-editor/helix/pull/4409))
906+- Scala ([#4353](https://github.com/helix-editor/helix/pull/4353), [#4697](https://github.com/helix-editor/helix/pull/4697), [#4701](https://github.com/helix-editor/helix/pull/4701))
907+- LaTeX ([#4528](https://github.com/helix-editor/helix/pull/4528), [#4922](https://github.com/helix-editor/helix/pull/4922))
908+- SQL ([#4529](https://github.com/helix-editor/helix/pull/4529))
909+- Python ([#4560](https://github.com/helix-editor/helix/pull/4560))
910+- Bash/Zsh ([#4582](https://github.com/helix-editor/helix/pull/4582))
911+- Nu ([#4583](https://github.com/helix-editor/helix/pull/4583))
912+- Julia ([#4588](https://github.com/helix-editor/helix/pull/4588))
913+- Typescript ([#4703](https://github.com/helix-editor/helix/pull/4703))
914+- Meson ([#4572](https://github.com/helix-editor/helix/pull/4572))
915+- Haskell ([#4800](https://github.com/helix-editor/helix/pull/4800))
916+- CMake ([#4809](https://github.com/helix-editor/helix/pull/4809))
917+- HTML ([#4829](https://github.com/helix-editor/helix/pull/4829), [#4881](https://github.com/helix-editor/helix/pull/4881))
918+- Java ([#4886](https://github.com/helix-editor/helix/pull/4886))
919+- Go ([#4906](https://github.com/helix-editor/helix/pull/4906), [#4969](https://github.com/helix-editor/helix/pull/4969), [#5010](https://github.com/helix-editor/helix/pull/5010))
920+- CSS ([#4882](https://github.com/helix-editor/helix/pull/4882))
921+- Racket ([#4915](https://github.com/helix-editor/helix/pull/4915))
922+- SCSS ([#5003](https://github.com/helix-editor/helix/pull/5003))
923+
924+Packaging:
925+
926+- Filter relevant source files in the Nix flake ([#3657](https://github.com/helix-editor/helix/pull/3657))
927+- Build a binary for `aarch64-linux` in the release CI ([038a91d](https://github.com/helix-editor/helix/commit/038a91d))
928+- Build an AppImage for `aarch64-linux` in the release CI ([b738031](https://github.com/helix-editor/helix/commit/b738031))
929+- Enable CI builds for `riscv64-linux` ([#3685](https://github.com/helix-editor/helix/pull/3685))
930+- Support preview releases in CI ([0090a2d](https://github.com/helix-editor/helix/commit/0090a2d))
931+- Strip binaries built in CI ([#3780](https://github.com/helix-editor/helix/pull/3780))
932+- Fix the development shell for the Nix Flake on `aarch64-darwin` ([#3810](https://github.com/helix-editor/helix/pull/3810))
933+- Raise the MSRV and create an MSRV policy ([#3896](https://github.com/helix-editor/helix/pull/3896), [#3913](https://github.com/helix-editor/helix/pull/3913), [#3961](https://github.com/helix-editor/helix/pull/3961))
934+- Fix Fish completions for `--config` and `--log` flags ([#3912](https://github.com/helix-editor/helix/pull/3912))
935+- Use builtin filenames option in Bash completion ([#4648](https://github.com/helix-editor/helix/pull/4648))
936+
937+# 22.08.1 (2022-09-01)
938+
939+This is a patch release that fixes a panic caused by closing splits or buffers. ([#3633](https://github.com/helix-editor/helix/pull/3633))
940+
941+# 22.08 (2022-08-31)
942+
943+A big _thank you_ to our contributors! This release had 87 contributors.
944+
945+As usual, the following is a summary of each of the changes since the last release.
946+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/22.05..22.08).
947+
948+Breaking changes:
949+
950+- Special keymap names for `+`, `;` and `%` have been replaced with those literal characters ([#2677](https://github.com/helix-editor/helix/pull/2677), [#3556](https://github.com/helix-editor/helix/pull/3556))
951+- `A-Left` and `A-Right` have become `C-Left` and `C-Right` for word-wise motion ([#2500](https://github.com/helix-editor/helix/pull/2500))
952+- The `catppuccin` theme's name has been corrected from `catpuccin` ([#2713](https://github.com/helix-editor/helix/pull/2713))
953+- `catppuccin` has been replaced by its variants, `catppuccin_frappe`, `catppuccin_latte`, `catppuccin_macchiato`, `catppuccin_mocha` ([#3281](https://github.com/helix-editor/helix/pull/3281))
954+- `C-n` and `C-p` have been removed from the default insert mode keymap ([#3340](https://github.com/helix-editor/helix/pull/3340))
955+- The `extend_line` command has been replaced with `extend_line_below` and a new `extend_line` command now exists ([#3046](https://github.com/helix-editor/helix/pull/3046))
956+
957+Features:
958+
959+- Add an integration testing harness ([#2359](https://github.com/helix-editor/helix/pull/2359))
960+- Indent guides ([#1796](https://github.com/helix-editor/helix/pull/1796), [906259c](https://github.com/helix-editor/helix/commit/906259c))
961+- Cursorline ([#2170](https://github.com/helix-editor/helix/pull/2170), [fde9e03](https://github.com/helix-editor/helix/commit/fde9e03))
962+- Select all instances of the symbol under the cursor (`<space>h`) ([#2738](https://github.com/helix-editor/helix/pull/2738))
963+- A picker for document and workspace LSP diagnostics (`<space>g`/`<space>G`) ([#2013](https://github.com/helix-editor/helix/pull/2013), [#2984](https://github.com/helix-editor/helix/pull/2984))
964+- Allow styling the mode indicator per-mode ([#2676](https://github.com/helix-editor/helix/pull/2676))
965+- Live preview for the theme picker ([#1798](https://github.com/helix-editor/helix/pull/1798))
966+- Configurable statusline ([#2434](https://github.com/helix-editor/helix/pull/2434))
967+- LSP SignatureHelp ([#1755](https://github.com/helix-editor/helix/pull/1755), [a8b123f](https://github.com/helix-editor/helix/commit/a8b123f))
968+- A picker for the jumplist ([#3033](https://github.com/helix-editor/helix/pull/3033))
969+- Configurable external formatter binaries ([#2942](https://github.com/helix-editor/helix/pull/2942))
970+- Bracketed paste support ([#3233](https://github.com/helix-editor/helix/pull/3233), [12ddd03](https://github.com/helix-editor/helix/commit/12ddd03))
971+
972+Commands:
973+
974+- `:insert-output` and `:append-output` which insert/append output from a shell command ([#2589](https://github.com/helix-editor/helix/pull/2589))
975+- The `t` textobject (`]t`/`[t`/`mit`/`mat`) for navigating tests ([#2807](https://github.com/helix-editor/helix/pull/2807))
976+- `C-Backspace` and `C-Delete` for word-wise deletion in prompts and pickers ([#2500](https://github.com/helix-editor/helix/pull/2500))
977+- `A-Delete` for forward word-wise deletion in insert mode ([#2500](https://github.com/helix-editor/helix/pull/2500))
978+- `C-t` for toggling the preview pane in pickers ([#3021](https://github.com/helix-editor/helix/pull/3021))
979+- `extend_line` now extends in the direction of the cursor ([#3046](https://github.com/helix-editor/helix/pull/3046))
980+
981+Usability improvements and fixes:
982+
983+- Fix tree-sitter parser builds on illumos ([#2602](https://github.com/helix-editor/helix/pull/2602))
984+- Remove empty stratch buffer from jumplists when removing ([5ed6223](https://github.com/helix-editor/helix/commit/5ed6223))
985+- Fix panic on undo after `shell_append_output` ([#2625](https://github.com/helix-editor/helix/pull/2625))
986+- Sort LSP edits by start range ([3d91c99](https://github.com/helix-editor/helix/commit/3d91c99))
987+- Be more defensive about LSP URI conversions ([6de6a3e](https://github.com/helix-editor/helix/commit/6de6a3e), [378f438](https://github.com/helix-editor/helix/commit/378f438))
988+- Ignore SendErrors when grammar builds fail ([#2641](https://github.com/helix-editor/helix/pull/2641))
989+- Append `set_line_ending` to document history ([#2649](https://github.com/helix-editor/helix/pull/2649))
990+- Use last prompt entry when empty ([b14c258](https://github.com/helix-editor/helix/commit/b14c258), [#2870](https://github.com/helix-editor/helix/pull/2870))
991+- Do not add extra line breaks in markdown lists ([#2689](https://github.com/helix-editor/helix/pull/2689))
992+- Disable dialyzer by default for ElixirLS ([#2710](https://github.com/helix-editor/helix/pull/2710))
993+- Refactor textobject node capture ([#2741](https://github.com/helix-editor/helix/pull/2741))
994+- Prevent re-selecting the same range with `expand_selection` ([#2760](https://github.com/helix-editor/helix/pull/2760))
995+- Introduce `keyword.storage` highlight scope ([#2731](https://github.com/helix-editor/helix/pull/2731))
996+- Handle symlinks more consistently ([#2718](https://github.com/helix-editor/helix/pull/2718))
997+- Improve markdown list rendering ([#2687](https://github.com/helix-editor/helix/pull/2687))
998+- Update auto-pairs and idle-timout settings when the config is reloaded ([#2736](https://github.com/helix-editor/helix/pull/2736))
999+- Fix panic on closing last buffer ([#2658](https://github.com/helix-editor/helix/pull/2658))
1000+- Prevent modifying jumplist until jumping to a reference ([#2670](https://github.com/helix-editor/helix/pull/2670))
1001+- Ensure `:quit` and `:quit!` take no arguments ([#2654](https://github.com/helix-editor/helix/pull/2654))
1002+- Fix crash due to cycles when replaying macros ([#2647](https://github.com/helix-editor/helix/pull/2647))
1003+- Pass LSP FormattingOptions ([#2635](https://github.com/helix-editor/helix/pull/2635))
1004+- Prevent showing colors when the health-check is piped ([#2836](https://github.com/helix-editor/helix/pull/2836))
1005+- Use character indexing for mouse selection ([#2839](https://github.com/helix-editor/helix/pull/2839))
1006+- Display the highest severity diagnostic for a line in the gutter ([#2835](https://github.com/helix-editor/helix/pull/2835))
1007+- Default the ruler color to red background ([#2669](https://github.com/helix-editor/helix/pull/2669))
1008+- Make `move_vertically` aware of tabs and wide characters ([#2620](https://github.com/helix-editor/helix/pull/2620))
1009+- Enable shellwords for Windows ([#2767](https://github.com/helix-editor/helix/pull/2767))
1010+- Add history suggestions to global search ([#2717](https://github.com/helix-editor/helix/pull/2717))
1011+- Fix the scrollbar's length proportional to total menu items ([#2860](https://github.com/helix-editor/helix/pull/2860))
1012+- Reset terminal modifiers for diagnostic text ([#2861](https://github.com/helix-editor/helix/pull/2861), [#2900](https://github.com/helix-editor/helix/pull/2900))
1013+- Redetect indents and line-endings after a Language Server replaces the document ([#2778](https://github.com/helix-editor/helix/pull/2778))
1014+- Check selection's visible width when copying on mouse click ([#2711](https://github.com/helix-editor/helix/pull/2711))
1015+- Fix edge-case in tree-sitter `expand_selection` command ([#2877](https://github.com/helix-editor/helix/pull/2877))
1016+- Add a single-width left margin for the completion popup ([#2728](https://github.com/helix-editor/helix/pull/2728))
1017+- Right-align the scrollbar in the completion popup ([#2754](https://github.com/helix-editor/helix/pull/2754))
1018+- Fix recursive macro crash and empty macro lockout ([#2902](https://github.com/helix-editor/helix/pull/2902))
1019+- Fix backwards character deletion on other whitespaces ([#2855](https://github.com/helix-editor/helix/pull/2855))
1020+- Add search and space/backspace bindings to view modes ([#2803](https://github.com/helix-editor/helix/pull/2803))
1021+- Add `--vsplit` and `--hsplit` CLI arguments for opening in splits ([#2773](https://github.com/helix-editor/helix/pull/2773), [#3073](https://github.com/helix-editor/helix/pull/3073))
1022+- Sort themes, languages and files inputs by score and name ([#2675](https://github.com/helix-editor/helix/pull/2675))
1023+- Highlight entire rows in ([#2939](https://github.com/helix-editor/helix/pull/2939))
1024+- Fix backwards selection duplication widening bug ([#2945](https://github.com/helix-editor/helix/pull/2945), [#3024](https://github.com/helix-editor/helix/pull/3024))
1025+- Skip serializing Option type DAP fields ([44f5963](https://github.com/helix-editor/helix/commit/44f5963))
1026+- Fix required `cwd` field in DAP `RunTerminalArguments` type ([85411be](https://github.com/helix-editor/helix/commit/85411be), [#3240](https://github.com/helix-editor/helix/pull/3240))
1027+- Add LSP `workspace/applyEdit` to client capabilities ([#3012](https://github.com/helix-editor/helix/pull/3012))
1028+- Respect count for repeating motion ([#3057](https://github.com/helix-editor/helix/pull/3057))
1029+- Respect count for selecting next/previous match ([#3056](https://github.com/helix-editor/helix/pull/3056))
1030+- Respect count for tree-sitter motions ([#3058](https://github.com/helix-editor/helix/pull/3058))
1031+- Make gutters padding optional ([#2996](https://github.com/helix-editor/helix/pull/2996))
1032+- Support pre-filling prompts ([#2459](https://github.com/helix-editor/helix/pull/2459), [#3259](https://github.com/helix-editor/helix/pull/3259))
1033+- Add statusline element to display file line-endings ([#3113](https://github.com/helix-editor/helix/pull/3113))
1034+- Keep jump and file history when using `:split` ([#3031](https://github.com/helix-editor/helix/pull/3031), [#3160](https://github.com/helix-editor/helix/pull/3160))
1035+- Make tree-sitter query `; inherits <language>` feature imperative ([#2470](https://github.com/helix-editor/helix/pull/2470))
1036+- Indent with tabs by default ([#3095](https://github.com/helix-editor/helix/pull/3095))
1037+- Fix non-msvc grammar compilation on Windows ([#3190](https://github.com/helix-editor/helix/pull/3190))
1038+- Add spacer element to the statusline ([#3165](https://github.com/helix-editor/helix/pull/3165), [255c173](https://github.com/helix-editor/helix/commit/255c173))
1039+- Make gutters padding automatic ([#3163](https://github.com/helix-editor/helix/pull/3163))
1040+- Add `code` for LSP `Diagnostic` type ([#3096](https://github.com/helix-editor/helix/pull/3096))
1041+- Add position percentage to the statusline ([#3168](https://github.com/helix-editor/helix/pull/3168))
1042+- Add a configurable and themable statusline separator string ([#3175](https://github.com/helix-editor/helix/pull/3175))
1043+- Use OR of all selections when `search_selection` acts on multiple selections ([#3138](https://github.com/helix-editor/helix/pull/3138))
1044+- Add clipboard information to logs and the healthcheck ([#3271](https://github.com/helix-editor/helix/pull/3271))
1045+- Fix align selection behavior on tabs ([#3276](https://github.com/helix-editor/helix/pull/3276))
1046+- Fix terminal cursor shape reset ([#3289](https://github.com/helix-editor/helix/pull/3289))
1047+- Add an `injection.include-unnamed-children` predicate to injections queries ([#3129](https://github.com/helix-editor/helix/pull/3129))
1048+- Add a `-c`/`--config` CLI flag for specifying config file location ([#2666](https://github.com/helix-editor/helix/pull/2666))
1049+- Detect indent-style in `:set-language` command ([#3330](https://github.com/helix-editor/helix/pull/3330))
1050+- Fix non-deterministic highlighting ([#3275](https://github.com/helix-editor/helix/pull/3275))
1051+- Avoid setting the stdin handle when not necessary ([#3248](https://github.com/helix-editor/helix/pull/3248), [#3379](https://github.com/helix-editor/helix/pull/3379))
1052+- Fix indent guide styling ([#3324](https://github.com/helix-editor/helix/pull/3324))
1053+- Fix tab highlight when tab is partially visible ([#3313](https://github.com/helix-editor/helix/pull/3313))
1054+- Add completion for nested settings ([#3183](https://github.com/helix-editor/helix/pull/3183))
1055+- Advertise WorkspaceSymbolClientCapabilities LSP client capability ([#3361](https://github.com/helix-editor/helix/pull/3361))
1056+- Remove duplicate entries from the theme picker ([#3439](https://github.com/helix-editor/helix/pull/3439))
1057+- Shorted output for grammar fetching and building ([#3396](https://github.com/helix-editor/helix/pull/3396))
1058+- Add a `tabpad` option for visible tab padding whitespace characters ([#3458](https://github.com/helix-editor/helix/pull/3458))
1059+- Make DAP external terminal provider configurable ([cb7615e](https://github.com/helix-editor/helix/commit/cb7615e))
1060+- Use health checkmark character with shorter width ([#3505](https://github.com/helix-editor/helix/pull/3505))
1061+- Reset document mode to normal on view focus loss ([e4c9d40](https://github.com/helix-editor/helix/commit/e4c9d40))
1062+- Render indented code-blocks in markdown ([#3503](https://github.com/helix-editor/helix/pull/3503))
1063+- Add WezTerm to DAP terminal provider defaults ([#3588](https://github.com/helix-editor/helix/pull/3588))
1064+- Derive `Document` language name from `languages.toml` `name` key ([#3338](https://github.com/helix-editor/helix/pull/3338))
1065+- Fix process spawning error handling ([#3349](https://github.com/helix-editor/helix/pull/3349))
1066+- Don't resolve links for `:o` completion ([8a4fbf6](https://github.com/helix-editor/helix/commit/8a4fbf6))
1067+- Recalculate completion after pasting into prompt ([e77b7d1](https://github.com/helix-editor/helix/commit/e77b7d1))
1068+- Fix extra selections with regex anchors ([#3598](https://github.com/helix-editor/helix/pull/3598))
1069+- Move mode transition logic to `handle_keymap_event` ([#2634](https://github.com/helix-editor/helix/pull/2634))
1070+- Add documents to view history when using the jumplist ([#3593](https://github.com/helix-editor/helix/pull/3593))
1071+- Prevent panic when loading tree-sitter queries ([fa1dc7e](https://github.com/helix-editor/helix/commit/fa1dc7e))
1072+- Discard LSP publishDiagnostic when LS is not initialized ([#3403](https://github.com/helix-editor/helix/pull/3403))
1073+- Refactor tree-sitter textobject motions as repeatable motions ([#3264](https://github.com/helix-editor/helix/pull/3264))
1074+- Avoid command execution hooks on closed docs ([#3613](https://github.com/helix-editor/helix/pull/3613))
1075+- Share `restore_term` code between panic and normal exits ([#2612](https://github.com/helix-editor/helix/pull/2612))
1076+- Show clipboard info in `--health` output ([#2947](https://github.com/helix-editor/helix/pull/2947))
1077+- Recalculate completion when going through prompt history ([#3193](https://github.com/helix-editor/helix/pull/3193))
1078+
1079+Themes:
1080+
1081+- Update `tokyonight` and `tokyonight_storm` themes ([#2606](https://github.com/helix-editor/helix/pull/2606))
1082+- Update `solarized_light` themes ([#2626](https://github.com/helix-editor/helix/pull/2626))
1083+- Fix `catpuccin` `ui.popup` theme ([#2644](https://github.com/helix-editor/helix/pull/2644))
1084+- Update selection style of `night_owl` ([#2668](https://github.com/helix-editor/helix/pull/2668))
1085+- Fix spelling of `catppuccin` theme ([#2713](https://github.com/helix-editor/helix/pull/2713))
1086+- Update `base16_default`'s `ui.menu` ([#2794](https://github.com/helix-editor/helix/pull/2794))
1087+- Add `noctis_bordo` ([#2830](https://github.com/helix-editor/helix/pull/2830))
1088+- Add `acme` ([#2876](https://github.com/helix-editor/helix/pull/2876))
1089+- Add `meliora` ([#2884](https://github.com/helix-editor/helix/pull/2884), [#2890](https://github.com/helix-editor/helix/pull/2890))
1090+- Add cursorline scopes to various themes ([33d287a](https://github.com/helix-editor/helix/commit/33d287a), [#2892](https://github.com/helix-editor/helix/pull/2892), [#2915](https://github.com/helix-editor/helix/pull/2915), [#2916](https://github.com/helix-editor/helix/pull/2916), [#2918](https://github.com/helix-editor/helix/pull/2918), [#2927](https://github.com/helix-editor/helix/pull/2927), [#2925](https://github.com/helix-editor/helix/pull/2925), [#2938](https://github.com/helix-editor/helix/pull/2938), [#2962](https://github.com/helix-editor/helix/pull/2962), [#3054](https://github.com/helix-editor/helix/pull/3054))
1091+- Add mode colors to various themes ([#2926](https://github.com/helix-editor/helix/pull/2926), [#2933](https://github.com/helix-editor/helix/pull/2933), [#2929](https://github.com/helix-editor/helix/pull/2929), [#3098](https://github.com/helix-editor/helix/pull/3098), [#3104](https://github.com/helix-editor/helix/pull/3104), [#3128](https://github.com/helix-editor/helix/pull/3128), [#3135](https://github.com/helix-editor/helix/pull/3135), [#3200](https://github.com/helix-editor/helix/pull/3200))
1092+- Add `nord_light` ([#2908](https://github.com/helix-editor/helix/pull/2908))
1093+- Update `night_owl` ([#2929](https://github.com/helix-editor/helix/pull/2929))
1094+- Update `autumn` ([2e70985](https://github.com/helix-editor/helix/commit/2e70985), [936ed3a](https://github.com/helix-editor/helix/commit/936ed3a))
1095+- Update `one_dark` ([#3011](https://github.com/helix-editor/helix/pull/3011))
1096+- Add `noctis` ([#3043](https://github.com/helix-editor/helix/pull/3043), [#3128](https://github.com/helix-editor/helix/pull/3128))
1097+- Update `boo_berry` ([#3191](https://github.com/helix-editor/helix/pull/3191))
1098+- Update `monokai` ([#3131](https://github.com/helix-editor/helix/pull/3131))
1099+- Add `ayu_dark`, `ayu_light`, `ayu_mirage` ([#3184](https://github.com/helix-editor/helix/pull/3184))
1100+- Update `onelight` ([#3226](https://github.com/helix-editor/helix/pull/3226))
1101+- Add `base16_transparent` ([#3216](https://github.com/helix-editor/helix/pull/3216), [b565fff](https://github.com/helix-editor/helix/commit/b565fff))
1102+- Add `flatwhite` ([#3236](https://github.com/helix-editor/helix/pull/3236))
1103+- Update `dark_plus` ([#3302](https://github.com/helix-editor/helix/pull/3302))
1104+- Add `doom_acario_dark` ([#3308](https://github.com/helix-editor/helix/pull/3308), [#3539](https://github.com/helix-editor/helix/pull/3539))
1105+- Add `rose_pine_moon` ([#3229](https://github.com/helix-editor/helix/pull/3229))
1106+- Update `spacebones_light` ([#3342](https://github.com/helix-editor/helix/pull/3342))
1107+- Fix typos in themes ([8deaebd](https://github.com/helix-editor/helix/commit/8deaebd), [#3412](https://github.com/helix-editor/helix/pull/3412))
1108+- Add `emacs` ([#3410](https://github.com/helix-editor/helix/pull/3410))
1109+- Add `papercolor-light` ([#3426](https://github.com/helix-editor/helix/pull/3426), [#3470](https://github.com/helix-editor/helix/pull/3470), [#3585](https://github.com/helix-editor/helix/pull/3585))
1110+- Add `penumbra+` ([#3398](https://github.com/helix-editor/helix/pull/3398))
1111+- Add `fleetish` ([#3591](https://github.com/helix-editor/helix/pull/3591), [#3607](https://github.com/helix-editor/helix/pull/3607))
1112+- Add `sonokai` ([#3595](https://github.com/helix-editor/helix/pull/3595))
1113+- Update all themes for theme lints ([#3587](https://github.com/helix-editor/helix/pull/3587))
1114+
1115+LSP:
1116+
1117+- V ([#2526](https://github.com/helix-editor/helix/pull/2526))
1118+- Prisma ([#2703](https://github.com/helix-editor/helix/pull/2703))
1119+- Clojure ([#2780](https://github.com/helix-editor/helix/pull/2780))
1120+- WGSL ([#2872](https://github.com/helix-editor/helix/pull/2872))
1121+- Elvish ([#2948](https://github.com/helix-editor/helix/pull/2948))
1122+- Idris ([#2971](https://github.com/helix-editor/helix/pull/2971))
1123+- Fortran ([#3025](https://github.com/helix-editor/helix/pull/3025))
1124+- Gleam ([#3139](https://github.com/helix-editor/helix/pull/3139))
1125+- Odin ([#3214](https://github.com/helix-editor/helix/pull/3214))
1126+
1127+New languages:
1128+
1129+- V ([#2526](https://github.com/helix-editor/helix/pull/2526))
1130+- EDoc ([#2640](https://github.com/helix-editor/helix/pull/2640))
1131+- JSDoc ([#2650](https://github.com/helix-editor/helix/pull/2650))
1132+- OpenSCAD ([#2680](https://github.com/helix-editor/helix/pull/2680))
1133+- Prisma ([#2703](https://github.com/helix-editor/helix/pull/2703))
1134+- Clojure ([#2780](https://github.com/helix-editor/helix/pull/2780))
1135+- Starlark ([#2903](https://github.com/helix-editor/helix/pull/2903))
1136+- Elvish ([#2948](https://github.com/helix-editor/helix/pull/2948))
1137+- Fortran ([#3025](https://github.com/helix-editor/helix/pull/3025))
1138+- Ungrammar ([#3048](https://github.com/helix-editor/helix/pull/3048))
1139+- SCSS ([#3074](https://github.com/helix-editor/helix/pull/3074))
1140+- Go Template ([#3091](https://github.com/helix-editor/helix/pull/3091))
1141+- Graphviz dot ([#3241](https://github.com/helix-editor/helix/pull/3241))
1142+- Cue ([#3262](https://github.com/helix-editor/helix/pull/3262))
1143+- Slint ([#3355](https://github.com/helix-editor/helix/pull/3355))
1144+- Beancount ([#3297](https://github.com/helix-editor/helix/pull/3297))
1145+- Taskwarrior ([#3468](https://github.com/helix-editor/helix/pull/3468))
1146+- xit ([#3521](https://github.com/helix-editor/helix/pull/3521))
1147+- ESDL ([#3526](https://github.com/helix-editor/helix/pull/3526))
1148+- Awk ([#3528](https://github.com/helix-editor/helix/pull/3528), [#3535](https://github.com/helix-editor/helix/pull/3535))
1149+- Pascal ([#3542](https://github.com/helix-editor/helix/pull/3542))
1150+
1151+Updated languages and queries:
1152+
1153+- Nix ([#2472](https://github.com/helix-editor/helix/pull/2472))
1154+- Elixir ([#2619](https://github.com/helix-editor/helix/pull/2619))
1155+- CPON ([#2643](https://github.com/helix-editor/helix/pull/2643))
1156+- Textobjects queries for Erlang, Elixir, Gleam ([#2661](https://github.com/helix-editor/helix/pull/2661))
1157+- Capture rust closures as function textobjects ([4a27e2d](https://github.com/helix-editor/helix/commit/4a27e2d))
1158+- Heex ([#2800](https://github.com/helix-editor/helix/pull/2800), [#3170](https://github.com/helix-editor/helix/pull/3170))
1159+- Add `<<=` operator highlighting for Rust ([#2805](https://github.com/helix-editor/helix/pull/2805))
1160+- Fix comment injection in JavaScript/TypeScript ([#2763](https://github.com/helix-editor/helix/pull/2763))
1161+- Nickel ([#2859](https://github.com/helix-editor/helix/pull/2859))
1162+- Add `Rakefile` and `Gemfile` to Ruby file-types ([#2875](https://github.com/helix-editor/helix/pull/2875))
1163+- Erlang ([#2910](https://github.com/helix-editor/helix/pull/2910), [ac669ad](https://github.com/helix-editor/helix/commit/ac669ad))
1164+- Markdown ([#2910](https://github.com/helix-editor/helix/pull/2910), [#3108](https://github.com/helix-editor/helix/pull/3108), [#3400](https://github.com/helix-editor/helix/pull/3400))
1165+- Bash ([#2910](https://github.com/helix-editor/helix/pull/2910))
1166+- Rust ([#2910](https://github.com/helix-editor/helix/pull/2910), [#3397](https://github.com/helix-editor/helix/pull/3397))
1167+- Edoc ([#2910](https://github.com/helix-editor/helix/pull/2910))
1168+- HTML ([#2910](https://github.com/helix-editor/helix/pull/2910))
1169+- Make ([#2910](https://github.com/helix-editor/helix/pull/2910))
1170+- TSQ ([#2910](https://github.com/helix-editor/helix/pull/2910), [#2960](https://github.com/helix-editor/helix/pull/2960))
1171+- git-commit ([#2910](https://github.com/helix-editor/helix/pull/2910))
1172+- Use default fallback for Python indents ([9ae70cc](https://github.com/helix-editor/helix/commit/9ae70cc))
1173+- Add Haskell LSP roots ([#2954](https://github.com/helix-editor/helix/pull/2954))
1174+- Ledger ([#2936](https://github.com/helix-editor/helix/pull/2936), [#2988](https://github.com/helix-editor/helix/pull/2988))
1175+- Nickel ([#2987](https://github.com/helix-editor/helix/pull/2987))
1176+- JavaScript/TypeScript ([#2961](https://github.com/helix-editor/helix/pull/2961), [#3219](https://github.com/helix-editor/helix/pull/3219), [#3213](https://github.com/helix-editor/helix/pull/3213), [#3280](https://github.com/helix-editor/helix/pull/3280), [#3301](https://github.com/helix-editor/helix/pull/3301))
1177+- GLSL ([#3051](https://github.com/helix-editor/helix/pull/3051))
1178+- Fix locals tracking in Rust ([#3027](https://github.com/helix-editor/helix/pull/3027), [#3212](https://github.com/helix-editor/helix/pull/3212), [#3345](https://github.com/helix-editor/helix/pull/3345))
1179+- Verilog ([#3158](https://github.com/helix-editor/helix/pull/3158))
1180+- Ruby ([#3173](https://github.com/helix-editor/helix/pull/3173), [#3527](https://github.com/helix-editor/helix/pull/3527))
1181+- Svelte ([#3147](https://github.com/helix-editor/helix/pull/3147))
1182+- Add Elixir and HEEx comment textobjects ([#3179](https://github.com/helix-editor/helix/pull/3179))
1183+- Python ([#3103](https://github.com/helix-editor/helix/pull/3103), [#3201](https://github.com/helix-editor/helix/pull/3201), [#3284](https://github.com/helix-editor/helix/pull/3284))
1184+- PHP ([#3317](https://github.com/helix-editor/helix/pull/3317))
1185+- Latex ([#3370](https://github.com/helix-editor/helix/pull/3370))
1186+- Clojure ([#3387](https://github.com/helix-editor/helix/pull/3387))
1187+- Swift ([#3461](https://github.com/helix-editor/helix/pull/3461))
1188+- C# ([#3480](https://github.com/helix-editor/helix/pull/3480), [#3494](https://github.com/helix-editor/helix/pull/3494))
1189+- Org ([#3489](https://github.com/helix-editor/helix/pull/3489))
1190+- Elm ([#3497](https://github.com/helix-editor/helix/pull/3497))
1191+- Dart ([#3419](https://github.com/helix-editor/helix/pull/3419))
1192+- Julia ([#3507](https://github.com/helix-editor/helix/pull/3507))
1193+- Fix Rust textobjects ([#3590](https://github.com/helix-editor/helix/pull/3590))
1194+- C ([00d88e5](https://github.com/helix-editor/helix/commit/00d88e5))
1195+- Update Rust ([0ef0ef9](https://github.com/helix-editor/helix/commit/0ef0ef9))
1196+
1197+Packaging:
1198+
1199+- Add `rust-analyzer` to Nix flake devShell ([#2739](https://github.com/helix-editor/helix/pull/2739))
1200+- Add cachix information to the Nix flake ([#2999](https://github.com/helix-editor/helix/pull/2999))
1201+- Pass makeWrapperArgs to wrapProgram in the Nix flake ([#3003](https://github.com/helix-editor/helix/pull/3003))
1202+- Add a way to override which grammars are built by Nix ([#3141](https://github.com/helix-editor/helix/pull/3141))
1203+- Add a GitHub actions release for `aarch64-macos` ([#3137](https://github.com/helix-editor/helix/pull/3137))
1204+- Add shell auto-completions for Elvish ([#3331](https://github.com/helix-editor/helix/pull/3331))
1205+
1206+# 22.05 (2022-05-28)
1207+
1208+An even bigger shout out than usual to all the contributors - we had a whopping
1209+110 contributors in this release! That's more than double the number of
1210+contributors as last release!
1211+
1212+Check out some of the highlights in the [news section](https://helix-editor.com/news/release-22-05-highlights/).
1213+
1214+As usual, the following is a summary of each of the changes since the last release.
1215+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/22.03..22.05).
1216+
1217+Breaking Changes:
1218+
1219+- Removed `C-j`, `C-k` bindings from file picker ([#1792](https://github.com/helix-editor/helix/pull/1792))
1220+- Replaced `C-f` with `C-d` and `C-b` with `C-u` bindings in file picker ([#1792](https://github.com/helix-editor/helix/pull/1792))
1221+- `A-hjkl` bindings have been moved to `A-pion` ([#2205](https://github.com/helix-editor/helix/pull/2205))
1222+- `A-Left`/`A-Right` have been moved to `C-Left`/`C-Right` ([#2193](https://github.com/helix-editor/helix/pull/2193))
1223+
1224+Features:
1225+
1226+- The indentation mechanism has been reworked ([#1562](https://github.com/helix-editor/helix/pull/1562), [#1908](https://github.com/helix-editor/helix/pull/1908))
1227+- Configurable gutters ([#1967](https://github.com/helix-editor/helix/pull/1967))
1228+- Support for local language configuration ([#1249](https://github.com/helix-editor/helix/pull/1249))
1229+- Configurable themed rulers ([#2060](https://github.com/helix-editor/helix/pull/2060))
1230+- Render visible whitespace ([e6b865e](https://github.com/helix-editor/helix/commit/e6b865e), [#2322](https://github.com/helix-editor/helix/pull/2322), [#2331](https://github.com/helix-editor/helix/pull/2331))
1231+
1232+Commands:
1233+
1234+- Paragraph motion and textobject (`]p`, `[p`) ([#1627](https://github.com/helix-editor/helix/pull/1627), [#1956](https://github.com/helix-editor/helix/pull/1956), [#1969](https://github.com/helix-editor/helix/pull/1969), [#1992](https://github.com/helix-editor/helix/pull/1992), [#2226](https://github.com/helix-editor/helix/pull/2226))
1235+- `:buffer-next`, `:buffer-previous` ([#1940](https://github.com/helix-editor/helix/pull/1940))
1236+- `:set-language` to set the buffers language ([#1866](https://github.com/helix-editor/helix/pull/1866), [#1996](https://github.com/helix-editor/helix/pull/1996))
1237+- Command for picking files from the current working directory (`Space-F`) ([#1600](https://github.com/helix-editor/helix/pull/1600), [#2308](https://github.com/helix-editor/helix/pull/2308))
1238+- `:write!` which creates non-existent subdirectories ([#1839](https://github.com/helix-editor/helix/pull/1839))
1239+- Add `m` textobject that selects closest surrounding pair ([de15d70](https://github.com/helix-editor/helix/commit/de15d70), [76175db](https://github.com/helix-editor/helix/commit/76175db))
1240+- `:pipe` typable command for piping selections ([#1972](https://github.com/helix-editor/helix/pull/1972))
1241+- `extend_line_above` which extends to previous lines ([#2117](https://github.com/helix-editor/helix/pull/2117))
1242+- `set_line_ending` which replaces line endings ([#1871](https://github.com/helix-editor/helix/pull/1871))
1243+- `:get-option` for getting the current value of an option (`:get`) ([#2231](https://github.com/helix-editor/helix/pull/2231))
1244+- `:run-shell-command` which does not interact with selections ([#1682](https://github.com/helix-editor/helix/pull/1682))
1245+- `:reflow` which hard-wraps selected text ([#2128](https://github.com/helix-editor/helix/pull/2128))
1246+- `commit_undo_checkpoint` which adds an undo checkpoint ([#2115](https://github.com/helix-editor/helix/pull/2115))
1247+- `:log-open` which opens the log file ([#2422](https://github.com/helix-editor/helix/pull/2422))
1248+- `transpose_view` which transposes window splits ([#2461](https://github.com/helix-editor/helix/pull/2461))
1249+- View-swapping: `swap_view_right`, `swap_view_left`, `swap_view_up`, `swap_view_down` ([#2445](https://github.com/helix-editor/helix/pull/2445))
1250+- `shrink_to_line_bounds` which shrinks selections to line-bounds ([#2450](https://github.com/helix-editor/helix/pull/2450))
1251+
1252+Usability improvements and fixes:
1253+
1254+- Handle broken pipes when piping `hx --health` through `head` ([#1876](https://github.com/helix-editor/helix/pull/1876))
1255+- Fix for `copy_selection` on newlines ([ab7885e](https://github.com/helix-editor/helix/commit/ab7885e), [236c6b7](https://github.com/helix-editor/helix/commit/236c6b7))
1256+- Use `win32yank` clipboard provider on WSL2 ([#1912](https://github.com/helix-editor/helix/pull/1912))
1257+- Jump to the next number on the line before incrementing ([#1778](https://github.com/helix-editor/helix/pull/1778))
1258+- Fix start position of next search ([#1904](https://github.com/helix-editor/helix/pull/1904))
1259+- Use check and X marks for health check output ([#1918](https://github.com/helix-editor/helix/pull/1918))
1260+- Clear terminal after switching to alternate screens ([#1944](https://github.com/helix-editor/helix/pull/1944))
1261+- Fix `toggle_comments` command on multiple selections ([#1882](https://github.com/helix-editor/helix/pull/1882))
1262+- Apply `ui.gutter` theming to empty gutter spans ([#2032](https://github.com/helix-editor/helix/pull/2032))
1263+- Use checkboxes in `hx --health` output ([#1947](https://github.com/helix-editor/helix/pull/1947))
1264+- Pass unmapped keys through prompt regardless of modifiers ([764adbd](https://github.com/helix-editor/helix/commit/764adbd))
1265+- LSP: pull formatting options from config ([c18de0e](https://github.com/helix-editor/helix/commit/c18de0e))
1266+- LSP: provide `rootPath` ([84e799f](https://github.com/helix-editor/helix/commit/84e799f))
1267+- LSP: implement `workspace_folders` ([8adf0c1](https://github.com/helix-editor/helix/commit/8adf0c1))
1268+- LSP: fix auto-import ([#2088](https://github.com/helix-editor/helix/pull/2088))
1269+- Send active diagnostic to LSP when requesting code actions ([#2005](https://github.com/helix-editor/helix/pull/2005))
1270+- Prevent panic when parsing malformed LSP `PublishDiagnostic` ([#2160](https://github.com/helix-editor/helix/pull/2160))
1271+- Restore document state on completion cancel ([#2096](https://github.com/helix-editor/helix/pull/2096))
1272+- Only merge top-level array when merging `languages.toml` ([#2145](https://github.com/helix-editor/helix/pull/2145), [#2215](https://github.com/helix-editor/helix/pull/2215))
1273+- Fix open on multiline selection ([#2161](https://github.com/helix-editor/helix/pull/2161))
1274+- Allow re-binding `0` if it is not used in a count ([#2174](https://github.com/helix-editor/helix/pull/2174))
1275+- Fix `ctrl-u` behavior in insert mode ([#1957](https://github.com/helix-editor/helix/pull/1957))
1276+- Check LSP rename capabilities before sending rename action ([#2203](https://github.com/helix-editor/helix/pull/2203))
1277+- Register the `publish_diagnostics` LSP capability ([#2241](https://github.com/helix-editor/helix/pull/2241))
1278+- Fix paste direction for typed paste commands ([#2288](https://github.com/helix-editor/helix/pull/2288))
1279+- Improve handling of buffer-close ([#1397](https://github.com/helix-editor/helix/pull/1397))
1280+- Extend the tutor file ([#2133](https://github.com/helix-editor/helix/pull/2133))
1281+- Treat slashes as word separators in prompts ([#2315](https://github.com/helix-editor/helix/pull/2315))
1282+- Auto-complete directory members ([#1682](https://github.com/helix-editor/helix/pull/1682))
1283+- Allow disabling format-on-save as a global editor setting ([#2321](https://github.com/helix-editor/helix/pull/2321))
1284+- Wrap command palette in overlay ([#2378](https://github.com/helix-editor/helix/pull/2378))
1285+- Prevent selections from collapsing when inserting newlines ([#2414](https://github.com/helix-editor/helix/pull/2414))
1286+- Allow configuration of LSP request timeout ([#2405](https://github.com/helix-editor/helix/pull/2405))
1287+- Use debug console on Windows for DAP terminal ([#2294](https://github.com/helix-editor/helix/pull/2294))
1288+- Exclude cursor when deleting with `C-w` in insert mode ([#2431](https://github.com/helix-editor/helix/pull/2431))
1289+- Prevent panics from LSP parsing errors ([7ae6cad](https://github.com/helix-editor/helix/commit/7ae6cad))
1290+- Prevent panics from LSP responses without requests ([#2475](https://github.com/helix-editor/helix/pull/2475))
1291+- Fix scroll rate for documentation popups ([#2497](https://github.com/helix-editor/helix/pull/2497))
1292+- Support inserting into prompts from registers ([#2458](https://github.com/helix-editor/helix/pull/2458))
1293+- Separate theme scopes for diagnostic types ([#2437](https://github.com/helix-editor/helix/pull/2437))
1294+- Use `ui.menu` instead of `ui.statusline` for command completion menu theming ([82fb217](https://github.com/helix-editor/helix/commit/82fb217))
1295+- Panic when reloading a shrunk file ([#2506](https://github.com/helix-editor/helix/pull/2506))
1296+- Add theme key for picker separator ([#2523](https://github.com/helix-editor/helix/pull/2523))
1297+
1298+Themes:
1299+
1300+- Remove `ui.text` background from dark_plus ([#1950](https://github.com/helix-editor/helix/pull/1950))
1301+- Add `boo_berry` ([#1962](https://github.com/helix-editor/helix/pull/1962))
1302+- Update `dark_plus` markup colors ([#1989](https://github.com/helix-editor/helix/pull/1989))
1303+- Update `dark_plus` `tag` and `ui.menu.selected` colors ([#2014](https://github.com/helix-editor/helix/pull/2014))
1304+- Add `dracula_at_night` ([#2008](https://github.com/helix-editor/helix/pull/2008))
1305+- Improve `dracula` selection theming ([#2077](https://github.com/helix-editor/helix/pull/2077))
1306+- Remove dim attribute on `onedark` line-number gutter ([#2155](https://github.com/helix-editor/helix/pull/2155))
1307+- Add `tokyonight` ([#2162](https://github.com/helix-editor/helix/pull/2162))
1308+- Use border colors from the original `dark_plus` theme ([#2186](https://github.com/helix-editor/helix/pull/2186))
1309+- Add `autumn` ([#2212](https://github.com/helix-editor/helix/pull/2212), [#2270](https://github.com/helix-editor/helix/pull/2270), [#2531](https://github.com/helix-editor/helix/pull/2531))
1310+- Add `tokyonight_storm` ([#2240](https://github.com/helix-editor/helix/pull/2240))
1311+- Add `pop-dark` ([#2189](https://github.com/helix-editor/helix/pull/2189))
1312+- Fix `base16_terminal` theme using incorrect ansi-color ([#2279](https://github.com/helix-editor/helix/pull/2279))
1313+- Add `onelight` ([#2287](https://github.com/helix-editor/helix/pull/2287), [#2323](https://github.com/helix-editor/helix/pull/2323))
1314+- Add `ui.virtual` scopes to `onedark` theme ([3626e38](https://github.com/helix-editor/helix/commit/3626e38))
1315+- Add `night_owl` ([#2330](https://github.com/helix-editor/helix/pull/2330))
1316+- Use yellow foreground and red background for `monokai_pro_spectrum` ([#2433](https://github.com/helix-editor/helix/pull/2433))
1317+- Add `snazzy` ([#2473](https://github.com/helix-editor/helix/pull/2473))
1318+- Update `dark_plus` constructor color ([8e8d4ba](https://github.com/helix-editor/helix/commit/8e8d4ba))
1319+- Add `ui.menu` to the default theme ([e7e13dc](https://github.com/helix-editor/helix/commit/e7e13dc))
1320+- Add `ui.menu` to any themes missing the key ([9be810f](https://github.com/helix-editor/helix/commit/9be810f))
1321+- Add `catppuccin` ([#2546](https://github.com/helix-editor/helix/pull/2546), [7160e74](https://github.com/helix-editor/helix/commit/7160e74))
1322+
1323+LSP:
1324+
1325+- Use texlab for latex ([#1922](https://github.com/helix-editor/helix/pull/1922))
1326+- HTML ([#2018](https://github.com/helix-editor/helix/pull/2018))
1327+- JSON ([#2024](https://github.com/helix-editor/helix/pull/2024))
1328+- CSS ([#2025](https://github.com/helix-editor/helix/pull/2025))
1329+- PHP ([#2031](https://github.com/helix-editor/helix/pull/2031))
1330+- Swift ([#2033](https://github.com/helix-editor/helix/pull/2033))
1331+- OCaml ([#2035](https://github.com/helix-editor/helix/pull/2035))
1332+- Vue ([#2043](https://github.com/helix-editor/helix/pull/2043))
1333+- Yaml ([#2234](https://github.com/helix-editor/helix/pull/2234))
1334+- Vala ([#2243](https://github.com/helix-editor/helix/pull/2243))
1335+- TOML ([#2302](https://github.com/helix-editor/helix/pull/2302))
1336+- Java ([#2511](https://github.com/helix-editor/helix/pull/2511))
1337+- Lua ([#2560](https://github.com/helix-editor/helix/pull/2560))
1338+- Verilog ([#2552](https://github.com/helix-editor/helix/pull/2552))
1339+
1340+New Languages:
1341+
1342+- JSX ([#1906](https://github.com/helix-editor/helix/pull/1906), [a24fb17](https://github.com/helix-editor/helix/commit/a24fb17), [855e438](https://github.com/helix-editor/helix/commit/855e438), [#1921](https://github.com/helix-editor/helix/pull/1921))
1343+- Rust Object Notation (RON) ([#1925](https://github.com/helix-editor/helix/pull/1925))
1344+- R and R Markdown ([#1998](https://github.com/helix-editor/helix/pull/1998))
1345+- Swift ([#2033](https://github.com/helix-editor/helix/pull/2033))
1346+- EJS and ERB ([#2055](https://github.com/helix-editor/helix/pull/2055))
1347+- EEx ([9d095e0](https://github.com/helix-editor/helix/commit/9d095e0))
1348+- HEEx ([4836bb3](https://github.com/helix-editor/helix/commit/4836bb3), [#2149](https://github.com/helix-editor/helix/pull/2149))
1349+- SQL ([#2097](https://github.com/helix-editor/helix/pull/2097))
1350+- GDScript ([#1985](https://github.com/helix-editor/helix/pull/1985))
1351+- Nickel ([#2173](https://github.com/helix-editor/helix/pull/2173), [#2320](https://github.com/helix-editor/helix/pull/2320))
1352+- `go.mod` and `go.work` ([#2197](https://github.com/helix-editor/helix/pull/2197))
1353+- Nushell ([#2225](https://github.com/helix-editor/helix/pull/2225))
1354+- Vala ([#2243](https://github.com/helix-editor/helix/pull/2243))
1355+- Hare ([#2289](https://github.com/helix-editor/helix/pull/2289), [#2480](https://github.com/helix-editor/helix/pull/2480))
1356+- DeviceTree ([#2329](https://github.com/helix-editor/helix/pull/2329))
1357+- Cairo ([7387905](https://github.com/helix-editor/helix/commit/7387905))
1358+- CPON ([#2355](https://github.com/helix-editor/helix/pull/2355), [#2424](https://github.com/helix-editor/helix/pull/2424))
1359+- git-ignore ([#2397](https://github.com/helix-editor/helix/pull/2397))
1360+- git-attributes ([#2397](https://github.com/helix-editor/helix/pull/2397))
1361+- Odin ([#2399](https://github.com/helix-editor/helix/pull/2399), [#2464](https://github.com/helix-editor/helix/pull/2464))
1362+- Meson ([#2314](https://github.com/helix-editor/helix/pull/2314))
1363+- SSH Client Config ([#2498](https://github.com/helix-editor/helix/pull/2498))
1364+- Scheme ([d25bae8](https://github.com/helix-editor/helix/commit/d25bae8))
1365+- Verilog ([#2552](https://github.com/helix-editor/helix/pull/2552))
1366+
1367+Updated Languages and Queries:
1368+
1369+- Erlang ([e2a5071](https://github.com/helix-editor/helix/commit/e2a5071), [#2149](https://github.com/helix-editor/helix/pull/2149), [82da9bd](https://github.com/helix-editor/helix/commit/82da9bd))
1370+- Elixir ([1819478](https://github.com/helix-editor/helix/commit/1819478), [8c3c901](https://github.com/helix-editor/helix/commit/8c3c901), [4ac94a5](https://github.com/helix-editor/helix/commit/4ac94a5))
1371+- Gleam ([7cd6050](https://github.com/helix-editor/helix/commit/7cd6050), [45dd540](https://github.com/helix-editor/helix/commit/45dd540))
1372+- Bash ([#1917](https://github.com/helix-editor/helix/pull/1917))
1373+- JavaScript ([#2140](https://github.com/helix-editor/helix/pull/2140))
1374+- Ruby textobject queries ([#2143](https://github.com/helix-editor/helix/pull/2143))
1375+- Fix Golang textobject queries ([#2153](https://github.com/helix-editor/helix/pull/2153))
1376+- Add more bash and HCL file extensions ([#2201](https://github.com/helix-editor/helix/pull/2201))
1377+- Divide HCL and tfvars into separate languages ([#2244](https://github.com/helix-editor/helix/pull/2244))
1378+- Use JavaScript for `cjs` files ([#2387](https://github.com/helix-editor/helix/pull/2387))
1379+- Use Perl for `t` files ([#2395](https://github.com/helix-editor/helix/pull/2395))
1380+- Use `markup.list` scopes for lists ([#2401](https://github.com/helix-editor/helix/pull/2401))
1381+- Use PHP for `inc` files ([#2440](https://github.com/helix-editor/helix/pull/2440))
1382+- Improve Rust textobjects ([#2494](https://github.com/helix-editor/helix/pull/2494), [10463fe](https://github.com/helix-editor/helix/commit/10463fe))
1383+- Python ([#2451](https://github.com/helix-editor/helix/pull/2451))
1384+
1385+Packaging:
1386+
1387+- Use `builtins.fromTOML` in Nix Flake on Nix 2.6+ ([#1892](https://github.com/helix-editor/helix/pull/1892))
1388+- Shell auto-completion files are now available ([#2022](https://github.com/helix-editor/helix/pull/2022))
1389+- Create an AppImage on release ([#2089](https://github.com/helix-editor/helix/pull/2089))
1390+
1391+# 22.03 (2022-03-28)
1392+
1393+A big shout out to all the contributors! We had 51 contributors in this release.
1394+
1395+This release is particularly large and featureful. Check out some of the
1396+highlights in the [news section](https://helix-editor.com/news/release-22-03-highlights/).
1397+
1398+As usual, the following is a summary of each of the changes since the last release.
1399+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/v0.6.0..22.03).
1400+
1401+Breaking changes:
1402+
1403+- LSP config now lives under `editor.lsp` ([#1868](https://github.com/helix-editor/helix/pull/1868))
1404+- Expand-selection was moved from `]o` to `Alt-h` ([#1495](https://github.com/helix-editor/helix/pull/1495))
1405+
1406+Features:
1407+
1408+- Experimental Debug Adapter Protocol (DAP) support ([#574](https://github.com/helix-editor/helix/pull/574))
1409+- Primary cursor shape may now be customized per mode ([#1154](https://github.com/helix-editor/helix/pull/1154))
1410+- Overhaul incremental highlights and enable combined injections ([`6728344..4080341`](https://github.com/helix-editor/helix/compare/6728344..4080341))
1411+- Allow specifying file start position ([#445](https://github.com/helix-editor/helix/pull/445), [#1676](https://github.com/helix-editor/helix/pull/1676))
1412+- Dynamic line numbers ([#1522](https://github.com/helix-editor/helix/pull/1522))
1413+- Show an info box with the contents of registers ([#980](https://github.com/helix-editor/helix/pull/980))
1414+- Wrap-around behavior during search is now configurable ([#1516](https://github.com/helix-editor/helix/pull/1516))
1415+- Tree-sitter textobjects motions for classes, functions, and parameters ([#1619](https://github.com/helix-editor/helix/pull/1619), [#1708](https://github.com/helix-editor/helix/pull/1708), [#1805](https://github.com/helix-editor/helix/pull/1805))
1416+- Command palette: a picker for available commands ([#1400](https://github.com/helix-editor/helix/pull/1400))
1417+- LSP `workspace/configuration` and `workspace/didChangeConfiguration` support ([#1684](https://github.com/helix-editor/helix/pull/1684))
1418+- `hx --health [LANG]` command ([#1669](https://github.com/helix-editor/helix/pull/1669))
1419+- Refactor of the tree-sitter grammar system ([#1659](https://github.com/helix-editor/helix/pull/1659))
1420+ - All submodules have been removed
1421+ - New `hx --grammar {fetch|build}` flags for fetching and building tree-sitter grammars
1422+ - A custom grammar selection may now be declared with the `use-grammars` key in `languages.toml`
1423+
1424+Commands:
1425+
1426+- `:cquit!` - quit forcefully with a non-zero exit-code ([#1414](https://github.com/helix-editor/helix/pull/1414))
1427+- `shrink_selection` - shrink the selection to a child tree-sitter node (`Alt-j`, [#1340](https://github.com/helix-editor/helix/pull/1340))
1428+- `:tree-sitter-subtree` - show the tree-sitter subtree under the primary selection ([#1453](https://github.com/helix-editor/helix/pull/1453), [#1524](https://github.com/helix-editor/helix/pull/1524))
1429+- Add `Alt-Backspace`, `Alt-<`, `Alt->`, and `Ctrl-j` to insert mode ([#1441](https://github.com/helix-editor/helix/pull/1441))
1430+- `select_next_sibling`, `select_prev_sibling` - select next and previous tree-sitter nodes (`Alt-l` and `Alt-h`, [#1495](https://github.com/helix-editor/helix/pull/1495))
1431+- `:buffer-close-all`, `:buffer-close-all!`, `:buffer-close-others`, and `:buffer-close-others!` ([#1677](https://github.com/helix-editor/helix/pull/1677))
1432+- `:vsplit-new` and `:hsplit-new` - open vertical and horizontal splits with new scratch buffers ([#1763](https://github.com/helix-editor/helix/pull/1763))
1433+- `:open-config` to open the config file and `:refresh-config` to refresh config after changes ([#1771](https://github.com/helix-editor/helix/pull/1771), [#1803](https://github.com/helix-editor/helix/pull/1803))
1434+
1435+Usability improvements and fixes:
1436+
1437+- Prevent `:cquit` from ignoring unsaved changes ([#1414](https://github.com/helix-editor/helix/pull/1414))
1438+- Scrolling view keeps selections ([#1420](https://github.com/helix-editor/helix/pull/1420))
1439+- Only use shellwords parsing on unix platforms ([`7767703`](https://github.com/helix-editor/helix/commit/7767703))
1440+- Fix slash in search selector status message ([#1449](https://github.com/helix-editor/helix/pull/1449))
1441+- Use `std::path::MAIN_SEPARATOR` to determine completion ([`3e4f815`](https://github.com/helix-editor/helix/commit/3e4f815))
1442+- Expand to current node with `expand_selection` when the node has no children ([#1454](https://github.com/helix-editor/helix/pull/1454))
1443+- Add vertical and horizontal splits to the buffer picker ([#1502](https://github.com/helix-editor/helix/pull/1502))
1444+- Use the correct language ID for JavaScript & TypeScript LSP ([#1466](https://github.com/helix-editor/helix/pull/1466))
1445+- Run format command for all buffers being written ([#1444](https://github.com/helix-editor/helix/pull/1444))
1446+- Fix panics during resizing ([#1408](https://github.com/helix-editor/helix/pull/1408))
1447+- Fix auto-pairs with CRLF ([#1470](https://github.com/helix-editor/helix/pull/1470))
1448+- Fix picker scrolling when the bottom is reached ([#1567](https://github.com/helix-editor/helix/pull/1567))
1449+- Use markup themes for the markdown component ([#1363](https://github.com/helix-editor/helix/pull/1363))
1450+- Automatically commit changes to history if not in insert mode ([`2a7ae96`](https://github.com/helix-editor/helix/commit/2a7ae96))
1451+- Render code-actions as a menu and add padding to popup ([`094a0aa`](https://github.com/helix-editor/helix/commit/094a0aa))
1452+- Only render menu scrollbar if the menu doesn't fit ([`f10a06f`](https://github.com/helix-editor/helix/commit/f10a06f), [`36b975c`](https://github.com/helix-editor/helix/commit/36b975c))
1453+- Parse git revision instead of tag for version ([`d3221b0`](https://github.com/helix-editor/helix/commit/d3221b0), [#1674](https://github.com/helix-editor/helix/pull/1674))
1454+- Fix incorrect last modified buffer ([#1621](https://github.com/helix-editor/helix/pull/1621))
1455+- Add `PageUp`, `PageDown`, `Ctrl-u`, `Ctrl-d`, `Home`, `End` bindings to the file picker ([#1612](https://github.com/helix-editor/helix/pull/1612))
1456+- Display buffer IDs in the buffer picker ([#1134](https://github.com/helix-editor/helix/pull/1134))
1457+- Allow multi-line prompt documentation ([`2af0432`](https://github.com/helix-editor/helix/commit/2af0432))
1458+- Ignore the `.git` directory from the file picker ([#1604](https://github.com/helix-editor/helix/pull/1604))
1459+- Allow separate styling for markup heading levels ([#1618](https://github.com/helix-editor/helix/pull/1618))
1460+- Automatically close popups ([#1285](https://github.com/helix-editor/helix/pull/1285))
1461+- Allow auto-pairs tokens to be configured ([#1624](https://github.com/helix-editor/helix/pull/1624))
1462+- Don't indent empty lines in `indent` command ([#1653](https://github.com/helix-editor/helix/pull/1653))
1463+- Ignore `Enter` keypress when a menu has no selection ([#1704](https://github.com/helix-editor/helix/pull/1704))
1464+- Show errors when surround deletions and replacements fail ([#1709](https://github.com/helix-editor/helix/pull/1709))
1465+- Show infobox hints for `mi` and `ma` ([#1686](https://github.com/helix-editor/helix/pull/1686))
1466+- Highlight matching text in file picker suggestions ([#1635](https://github.com/helix-editor/helix/pull/1635))
1467+- Allow capturing multiple nodes in textobject queries ([#1611](https://github.com/helix-editor/helix/pull/1611))
1468+- Make repeat operator work with completion edits ([#1640](https://github.com/helix-editor/helix/pull/1640))
1469+- Save to the jumplist when searching ([#1718](https://github.com/helix-editor/helix/pull/1718))
1470+- Fix bug with auto-replacement of components in compositor ([#1711](https://github.com/helix-editor/helix/pull/1711))
1471+- Use Kakoune logic for `align_selection` ([#1675](https://github.com/helix-editor/helix/pull/1675))
1472+- Fix `follows` for `nixpkgs` in `flake.nix` ([#1729](https://github.com/helix-editor/helix/pull/1729))
1473+- Performance improvements for the picker ([`78fba86`](https://github.com/helix-editor/helix/commit/78fba86))
1474+- Rename infobox theme scopes ([#1741](https://github.com/helix-editor/helix/pull/1741))
1475+- Fallback to broader scopes if a theme scope is not found ([#1714](https://github.com/helix-editor/helix/pull/1714))
1476+- Add arrow-keys bindings for tree-sitter sibling selection commands ([#1724](https://github.com/helix-editor/helix/pull/1724))
1477+- Fix a bug in LSP when creating a file in a folder that does not exist ([#1775](https://github.com/helix-editor/helix/pull/1775))
1478+- Use `^` and `$` regex location assertions for search ([#1793](https://github.com/helix-editor/helix/pull/1793))
1479+- Fix register names in `insert_register` command ([#1751](https://github.com/helix-editor/helix/pull/1751))
1480+- Perform extend line for all selections ([#1804](https://github.com/helix-editor/helix/pull/1804))
1481+- Prevent panic when moving in an empty picker ([#1786](https://github.com/helix-editor/helix/pull/1786))
1482+- Fix line number calculations for non CR/CRLF line breaks ([`b4a282f`](https://github.com/helix-editor/helix/commit/b4a282f), [`0b96201`](https://github.com/helix-editor/helix/commit/0b96201))
1483+- Deploy documentation for `master` builds separately from release docs ([#1783](https://github.com/helix-editor/helix/pull/1783))
1484+
1485+Themes:
1486+
1487+- Add everforest_light ([#1412](https://github.com/helix-editor/helix/pull/1412))
1488+- Add gruvbox_light ([#1509](https://github.com/helix-editor/helix/pull/1509))
1489+- Add modified background to dracula popup ([#1434](https://github.com/helix-editor/helix/pull/1434))
1490+- Markup support for monokai pro themes ([#1553](https://github.com/helix-editor/helix/pull/1553))
1491+- Markup support for dracula theme ([#1554](https://github.com/helix-editor/helix/pull/1554))
1492+- Add `tag` to gruvbox theme ([#1555](https://github.com/helix-editor/helix/pull/1555))
1493+- Markup support for remaining themes ([#1525](https://github.com/helix-editor/helix/pull/1525))
1494+- Serika light and dark ([#1566](https://github.com/helix-editor/helix/pull/1566))
1495+- Fix rose_pine and rose_pine_dawn popup background color ([#1606](https://github.com/helix-editor/helix/pull/1606))
1496+- Fix hover menu item text color in base16 themes ([#1668](https://github.com/helix-editor/helix/pull/1668))
1497+- Update markup heading styles for everforest ([#1687](https://github.com/helix-editor/helix/pull/1687))
1498+- Update markup heading styles for rose_pine themes ([#1706](https://github.com/helix-editor/helix/pull/1706))
1499+- Style bogster cursors ([`6a6a9ab`](https://github.com/helix-editor/helix/commit/6a6a9ab))
1500+- Fix `ui.selection` in rose_pine themes ([#1716](https://github.com/helix-editor/helix/pull/1716))
1501+- Use distinct colors for cursor and matched pair in gruvbox ([#1791](https://github.com/helix-editor/helix/pull/1791))
1502+- Improve colors for `ui.cursor.match` capture in some themes ([#1862](https://github.com/helix-editor/helix/pull/1862))
1503+
1504+LSP:
1505+
1506+- Add default language server for JavaScript ([#1457](https://github.com/helix-editor/helix/pull/1457))
1507+- Add `pom.xml` as maven root directory marker ([#1496](https://github.com/helix-editor/helix/pull/1496))
1508+- Haskell LSP ([#1556](https://github.com/helix-editor/helix/pull/1556))
1509+- C-sharp LSP support ([#1788](https://github.com/helix-editor/helix/pull/1788))
1510+- Clean up Julia LSP config ([#1811](https://github.com/helix-editor/helix/pull/1811))
1511+
1512+New Languages:
1513+
1514+- llvm-mir ([#1398](https://github.com/helix-editor/helix/pull/1398))
1515+- regex ([#1362](https://github.com/helix-editor/helix/pull/1362))
1516+- Make ([#1433](https://github.com/helix-editor/helix/pull/1433), [#1661](https://github.com/helix-editor/helix/pull/1661))
1517+- git-config ([#1426](https://github.com/helix-editor/helix/pull/1426))
1518+- Lean ([#1422](https://github.com/helix-editor/helix/pull/1422))
1519+- Elm ([#1514](https://github.com/helix-editor/helix/pull/1514))
1520+- GraphQL ([#1515](https://github.com/helix-editor/helix/pull/1515))
1521+- Twig ([#1602](https://github.com/helix-editor/helix/pull/1602))
1522+- Rescript ([#1616](https://github.com/helix-editor/helix/pull/1616), [#1863](https://github.com/helix-editor/helix/pull/1863))
1523+- Erlang ([#1657](https://github.com/helix-editor/helix/pull/1657))
1524+- Kotlin ([#1689](https://github.com/helix-editor/helix/pull/1689))
1525+- HCL ([#1705](https://github.com/helix-editor/helix/pull/1705), [#1726](https://github.com/helix-editor/helix/pull/1726))
1526+- Org ([#1845](https://github.com/helix-editor/helix/pull/1845))
1527+- Solidity ([#1848](https://github.com/helix-editor/helix/pull/1848), [#1854](https://github.com/helix-editor/helix/pull/1854))
1528+
1529+Updated Languages and Queries:
1530+
1531+- Textobject and indent queries for c and cpp ([#1293](https://github.com/helix-editor/helix/pull/1293))
1532+- Fix null and boolean constant highlights for nix ([#1428](https://github.com/helix-editor/helix/pull/1428))
1533+- Capture markdown link text as `markup.link.text` ([#1456](https://github.com/helix-editor/helix/pull/1456))
1534+- Update and re-enable Haskell ([#1417](https://github.com/helix-editor/helix/pull/1417), [#1520](https://github.com/helix-editor/helix/pull/1520))
1535+- Update Go with generics support ([`ddbf036`](https://github.com/helix-editor/helix/commit/ddbf036))
1536+- Use `tree-sitter-css` for SCSS files ([#1507](https://github.com/helix-editor/helix/pull/1507))
1537+- Update Zig ([#1501](https://github.com/helix-editor/helix/pull/1501))
1538+- Update PHP ([#1521](https://github.com/helix-editor/helix/pull/1521))
1539+- Expand language support for comment injections ([#1527](https://github.com/helix-editor/helix/pull/1527))
1540+- Use tree-sitter-bash for `.zshrc` and `.bashrc` ([`7d51042`](https://github.com/helix-editor/helix/commit/7d51042))
1541+- Use tree-sitter-bash for `.bash_profile` ([#1571](https://github.com/helix-editor/helix/pull/1571))
1542+- Use tree-sitter-bash for `.zshenv` and ZSH files ([#1574](https://github.com/helix-editor/helix/pull/1574))
1543+- IEx ([#1576](https://github.com/helix-editor/helix/pull/1576))
1544+- Textobject queries for PHP ([#1601](https://github.com/helix-editor/helix/pull/1601))
1545+- C-sharp highlight query improvements ([#1795](https://github.com/helix-editor/helix/pull/1795))
1546+- Git commit performance has been improved on large verbose commits ([#1838](https://github.com/helix-editor/helix/pull/1838))
1547+
1548+Packaging:
1549+
1550+- The submodules system has been replaced with command-line flags for fetching and building tree-sitter grammars ([#1659](https://github.com/helix-editor/helix/pull/1659))
1551+- Flake outputs are pushed to Cachix on each push to `master` ([#1721](https://github.com/helix-editor/helix/pull/1721))
1552+- Update flake's `nix-cargo-integration` to depend on `dream2nix` ([#1758](https://github.com/helix-editor/helix/pull/1758))
1553+
1554+# 0.6.0 (2022-01-04)
1555+
1556+Happy new year and a big shout out to all the contributors! We had 55 contributors in this release.
1557+
1558+Helix has popped up in DPorts and Fedora Linux via COPR ([#1270](https://github.com/helix-editor/helix/pull/1270))
1559+
1560+As usual the following is a brief summary, refer to the git history for a full log:
1561+
1562+Breaking changes:
1563+
1564+- fix: Normalize backtab into shift-tab
1565+
1566+Features:
1567+
1568+- Macros ([#1234](https://github.com/helix-editor/helix/pull/1234))
1569+- Add reverse search functionality ([#958](https://github.com/helix-editor/helix/pull/958))
1570+- Allow keys to be mapped to sequences of commands ([#589](https://github.com/helix-editor/helix/pull/589))
1571+- Make it possible to keybind TypableCommands ([#1169](https://github.com/helix-editor/helix/pull/1169))
1572+- Detect workspace root using language markers ([#1370](https://github.com/helix-editor/helix/pull/1370))
1573+- Add WORD textobject ([#991](https://github.com/helix-editor/helix/pull/991))
1574+- Add LSP rename_symbol (`space-r`) ([#1011](https://github.com/helix-editor/helix/pull/1011))
1575+- Added workspace_symbol_picker ([#1041](https://github.com/helix-editor/helix/pull/1041))
1576+- Detect filetype from shebang line ([#1001](https://github.com/helix-editor/helix/pull/1001))
1577+- Allow piping from stdin into a buffer on startup ([#996](https://github.com/helix-editor/helix/pull/996))
1578+- Add auto pairs for same-char pairs ([#1219](https://github.com/helix-editor/helix/pull/1219))
1579+- Update settings at runtime ([#798](https://github.com/helix-editor/helix/pull/798))
1580+- Enable thin LTO ([`cccc194`](https://github.com/helix-editor/helix/commit/cccc194))
1581+
1582+Commands:
1583+
1584+- `:wonly` -- window only ([#1057](https://github.com/helix-editor/helix/pull/1057))
1585+- buffer-close (`:bc`, `:bclose`) ([#1035](https://github.com/helix-editor/helix/pull/1035))
1586+- Add `:<line>` and `:goto <line>` commands ([#1128](https://github.com/helix-editor/helix/pull/1128))
1587+- `:sort` command ([#1288](https://github.com/helix-editor/helix/pull/1288))
1588+- Add m textobject for pair under cursor ([#961](https://github.com/helix-editor/helix/pull/961))
1589+- Implement "Goto next buffer / Goto previous buffer" commands ([#950](https://github.com/helix-editor/helix/pull/950))
1590+- Implement "Goto last modification" command ([#1067](https://github.com/helix-editor/helix/pull/1067))
1591+- Add trim_selections command ([#1092](https://github.com/helix-editor/helix/pull/1092))
1592+- Add movement shortcut for history ([#1088](https://github.com/helix-editor/helix/pull/1088))
1593+- Add command to inc/dec number under cursor ([#1027](https://github.com/helix-editor/helix/pull/1027))
1594+ - Add support for dates for increment/decrement
1595+- Align selections (`&`) ([#1101](https://github.com/helix-editor/helix/pull/1101))
1596+- Implement no-yank delete/change ([#1099](https://github.com/helix-editor/helix/pull/1099))
1597+- Implement black hole register ([#1165](https://github.com/helix-editor/helix/pull/1165))
1598+- `gf` as goto_file (`gf`) ([#1102](https://github.com/helix-editor/helix/pull/1102))
1599+- Add last modified file (`gm`) ([#1093](https://github.com/helix-editor/helix/pull/1093))
1600+- ensure_selections_forward ([#1393](https://github.com/helix-editor/helix/pull/1393))
1601+- Readline style insert mode ([#1039](https://github.com/helix-editor/helix/pull/1039))
1602+
1603+Usability improvements and fixes:
1604+
1605+- Detect filetype on `:write` ([#1141](https://github.com/helix-editor/helix/pull/1141))
1606+- Add single and double quotes to matching pairs ([#995](https://github.com/helix-editor/helix/pull/995))
1607+- Launch with defaults upon invalid config/theme (rather than panicking) ([#982](https://github.com/helix-editor/helix/pull/982))
1608+- If switching away from an empty scratch buffer, remove it ([#935](https://github.com/helix-editor/helix/pull/935))
1609+- Truncate the starts of file paths instead of the ends in picker ([#951](https://github.com/helix-editor/helix/pull/951))
1610+- Truncate the start of file paths in the StatusLine ([#1351](https://github.com/helix-editor/helix/pull/1351))
1611+- Prevent picker from previewing binaries or large file ([#939](https://github.com/helix-editor/helix/pull/939))
1612+- Inform when reaching undo/redo bounds ([#981](https://github.com/helix-editor/helix/pull/981))
1613+- search_impl will only align cursor center when it isn't in view ([#959](https://github.com/helix-editor/helix/pull/959))
1614+- Add `<C-h>`, `<C-u>`, `<C-d>`, Delete in prompt mode ([#1034](https://github.com/helix-editor/helix/pull/1034))
1615+- Restore screen position when aborting search ([#1047](https://github.com/helix-editor/helix/pull/1047))
1616+- Buffer picker: show is_modifier flag ([#1020](https://github.com/helix-editor/helix/pull/1020))
1617+- Add commit hash to version info, if present ([#957](https://github.com/helix-editor/helix/pull/957))
1618+- Implement indent-aware delete ([#1120](https://github.com/helix-editor/helix/pull/1120))
1619+- Jump to end char of surrounding pair from any cursor pos ([#1121](https://github.com/helix-editor/helix/pull/1121))
1620+- File picker configuration ([#988](https://github.com/helix-editor/helix/pull/988))
1621+- Fix surround cursor position calculation ([#1183](https://github.com/helix-editor/helix/pull/1183))
1622+- Accept count for goto_window ([#1033](https://github.com/helix-editor/helix/pull/1033))
1623+- Make kill_to_line_end behave like Emacs ([#1235](https://github.com/helix-editor/helix/pull/1235))
1624+- Only use a single documentation popup ([#1241](https://github.com/helix-editor/helix/pull/1241))
1625+- ui: popup: Don't allow scrolling past the end of content ([`3307f44c`](https://github.com/helix-editor/helix/commit/3307f44c))
1626+- Open files with spaces in filename, allow opening multiple files ([#1231](https://github.com/helix-editor/helix/pull/1231))
1627+- Allow paste commands to take a count ([#1261](https://github.com/helix-editor/helix/pull/1261))
1628+- Auto pairs selection ([#1254](https://github.com/helix-editor/helix/pull/1254))
1629+- Use a fuzzy matcher for commands ([#1386](https://github.com/helix-editor/helix/pull/1386))
1630+- Add `<C-s>` to pick word under doc cursor to prompt line & search completion ([#831](https://github.com/helix-editor/helix/pull/831))
1631+- Fix `:earlier`/`:later` missing changeset update ([#1069](https://github.com/helix-editor/helix/pull/1069))
1632+- Support extend for multiple goto ([#909](https://github.com/helix-editor/helix/pull/909))
1633+- Add arrow-key bindings for window switching ([#933](https://github.com/helix-editor/helix/pull/933))
1634+- Implement key ordering for info box ([#952](https://github.com/helix-editor/helix/pull/952))
1635+
1636+LSP:
1637+- Implement MarkedString rendering ([`e128a8702`](https://github.com/helix-editor/helix/commit/e128a8702))
1638+- Don't panic if init fails ([`d31bef7`](https://github.com/helix-editor/helix/commit/d31bef7))
1639+- Configurable diagnostic severity ([#1325](https://github.com/helix-editor/helix/pull/1325))
1640+- Resolve completion item ([#1315](https://github.com/helix-editor/helix/pull/1315))
1641+- Code action command support ([#1304](https://github.com/helix-editor/helix/pull/1304))
1642+
1643+Grammars:
1644+
1645+- Adds mint language server ([#974](https://github.com/helix-editor/helix/pull/974))
1646+- Perl ([#978](https://github.com/helix-editor/helix/pull/978)) ([#1280](https://github.com/helix-editor/helix/pull/1280))
1647+- GLSL ([#993](https://github.com/helix-editor/helix/pull/993))
1648+- Racket ([#1143](https://github.com/helix-editor/helix/pull/1143))
1649+- WGSL ([#1166](https://github.com/helix-editor/helix/pull/1166))
1650+- LLVM ([#1167](https://github.com/helix-editor/helix/pull/1167)) ([#1388](https://github.com/helix-editor/helix/pull/1388)) ([#1409](https://github.com/helix-editor/helix/pull/1409)) ([#1398](https://github.com/helix-editor/helix/pull/1398))
1651+- Markdown ([`49e06787`](https://github.com/helix-editor/helix/commit/49e06787))
1652+- Scala ([#1278](https://github.com/helix-editor/helix/pull/1278))
1653+- Dart ([#1250](https://github.com/helix-editor/helix/pull/1250))
1654+- Fish ([#1308](https://github.com/helix-editor/helix/pull/1308))
1655+- Dockerfile ([#1303](https://github.com/helix-editor/helix/pull/1303))
1656+- Git (commit, rebase, diff) ([#1338](https://github.com/helix-editor/helix/pull/1338)) ([#1402](https://github.com/helix-editor/helix/pull/1402)) ([#1373](https://github.com/helix-editor/helix/pull/1373))
1657+- tree-sitter-comment ([#1300](https://github.com/helix-editor/helix/pull/1300))
1658+- Highlight comments in c, cpp, cmake and llvm ([#1309](https://github.com/helix-editor/helix/pull/1309))
1659+- Improve yaml syntax highlighting highlighting ([#1294](https://github.com/helix-editor/helix/pull/1294))
1660+- Improve rust syntax highlighting ([#1295](https://github.com/helix-editor/helix/pull/1295))
1661+- Add textobjects and indents to cmake ([#1307](https://github.com/helix-editor/helix/pull/1307))
1662+- Add textobjects and indents to c and cpp ([#1293](https://github.com/helix-editor/helix/pull/1293))
1663+
1664+New themes:
1665+
1666+- Solarized dark ([#999](https://github.com/helix-editor/helix/pull/999))
1667+- Solarized light ([#1010](https://github.com/helix-editor/helix/pull/1010))
1668+- Spacebones light ([#1131](https://github.com/helix-editor/helix/pull/1131))
1669+- Monokai Pro ([#1206](https://github.com/helix-editor/helix/pull/1206))
1670+- Base16 Light and Terminal ([#1078](https://github.com/helix-editor/helix/pull/1078))
1671+ - and a default 16 color theme, truecolor detection
1672+- Dracula ([#1258](https://github.com/helix-editor/helix/pull/1258))
1673+
1674+# 0.5.0 (2021-10-28)
1675+
1676+A big shout out to all the contributors! We had 46 contributors in this release.
1677+
1678+Helix has popped up in [Scoop, FreeBSD Ports and Gentu GURU](https://repology.org/project/helix/versions)!
1679+
1680+The following is a quick rundown of the larger changes, there were many more
1681+(check the git history for more details).
1682+
1683+Breaking changes:
1684+
1685+- A couple of keymaps moved to resolve a few conflicting keybinds.
1686+ - Documentation popups were moved from `K` to `space+k`
1687+ - `K` is now `keep_selections` which filters selections to only keeps ones matching the regex
1688+ - `keep_primary_selection` moved from `space+space` to `,`
1689+ - `Alt-,` is now `remove_primary_selection` which keeps all selections except the primary one
1690+ - Opening files in a split moved from `C-h` to `C-s`
1691+- Some configuration options moved from a `[terminal]` section to `[editor]`. [Consult the documentation for more information.](https://docs.helix-editor.com/configuration.html)
1692+
1693+Features:
1694+
1695+- LSP compatibility greatly improved for some implementations (Julia, Python, Typescript)
1696+- Autocompletion! Completion now triggers automatically after a set idle timeout
1697+- Completion documentation is now displayed next to the popup ([#691](https://github.com/helix-editor/helix/pull/691))
1698+- Treesitter textobjects (select a function via `mf`, class via `mc`) ([#728](https://github.com/helix-editor/helix/pull/728))
1699+- Global search across entire workspace `space+/` ([#651](https://github.com/helix-editor/helix/pull/651))
1700+- Relative line number support ([#485](https://github.com/helix-editor/helix/pull/485))
1701+- Prompts now store a history ([`72cf86e`](https://github.com/helix-editor/helix/commit/72cf86e))
1702+- `:vsplit` and `:hsplit` commands ([#639](https://github.com/helix-editor/helix/pull/639))
1703+- `C-w h/j/k/l` can now be used to navigate between splits ([#860](https://github.com/helix-editor/helix/pull/860))
1704+- `C-j` and `C-k` are now alternative keybindings to `C-n` and `C-p` in the UI ([#876](https://github.com/helix-editor/helix/pull/876))
1705+- Shell commands (shell-pipe, pipe-to, shell-insert-output, shell-append-output, keep-pipe) ([#547](https://github.com/helix-editor/helix/pull/547))
1706+- Searching now defaults to smart case search (case insensitive unless uppercase is used) ([#761](https://github.com/helix-editor/helix/pull/761))
1707+- The preview pane was improved to highlight and center line ranges
1708+- The user `languages.toml` is now merged into defaults, no longer need to copy the entire file ([`dc57f8dc`](https://github.com/helix-editor/helix/commit/dc57f8dc))
1709+- Show hidden files in completions ([#648](https://github.com/helix-editor/helix/pull/648))
1710+- Grammar injections are now properly handled ([`dd0b15e`](https://github.com/helix-editor/helix/commit/dd0b15e))
1711+- `v` in select mode now switches back to normal mode ([#660](https://github.com/helix-editor/helix/pull/660))
1712+- View mode can now be triggered as a "sticky" mode ([#719](https://github.com/helix-editor/helix/pull/719))
1713+- `f`/`t` and object selection motions can now be repeated via `Alt-.` ([#891](https://github.com/helix-editor/helix/pull/891))
1714+- Statusline now displays total selection count and diagnostics counts for both errors and warnings ([#916](https://github.com/helix-editor/helix/pull/916))
1715+
1716+New grammars:
1717+
1718+- Ledger ([#572](https://github.com/helix-editor/helix/pull/572))
1719+- Protobuf ([#614](https://github.com/helix-editor/helix/pull/614))
1720+- Zig ([#631](https://github.com/helix-editor/helix/pull/631))
1721+- YAML ([#667](https://github.com/helix-editor/helix/pull/667))
1722+- Lua ([#665](https://github.com/helix-editor/helix/pull/665))
1723+- OCaml ([#666](https://github.com/helix-editor/helix/pull/666))
1724+- Svelte ([#733](https://github.com/helix-editor/helix/pull/733))
1725+- Vue ([#787](https://github.com/helix-editor/helix/pull/787))
1726+- Tree-sitter queries ([#845](https://github.com/helix-editor/helix/pull/845))
1727+- CMake ([#888](https://github.com/helix-editor/helix/pull/888))
1728+- Elixir (we switched over to the official grammar) ([`6c0786e`](https://github.com/helix-editor/helix/commit/6c0786e))
1729+- Language server definitions for Nix and Elixir ([#725](https://github.com/helix-editor/helix/pull/725))
1730+- Python now uses `pylsp` instead of `pyls`
1731+- Python now supports indentation
1732+
1733+New themes:
1734+
1735+- Monokai ([#628](https://github.com/helix-editor/helix/pull/628))
1736+- Everforest Dark ([#760](https://github.com/helix-editor/helix/pull/760))
1737+- Nord ([#799](https://github.com/helix-editor/helix/pull/799))
1738+- Base16 Default Dark ([#833](https://github.com/helix-editor/helix/pull/833))
1739+- Rose Pine ([#897](https://github.com/helix-editor/helix/pull/897))
1740+
1741+Fixes:
1742+
1743+- Fix crash on empty rust file ([#592](https://github.com/helix-editor/helix/pull/592))
1744+- Exit select mode after toggle comment ([#598](https://github.com/helix-editor/helix/pull/598))
1745+- Pin popups with no positioning to the initial position ([`12ea3888`](https://github.com/helix-editor/helix/commit/12ea3888))
1746+- xsel copy should not freeze the editor ([`6dd7dc4`](https://github.com/helix-editor/helix/commit/6dd7dc4))
1747+- `*` now only sets the search register and doesn't jump to the next occurrence ([`3426285`](https://github.com/helix-editor/helix/commit/3426285))
1748+- Goto line start/end commands extend when in select mode ([#739](https://github.com/helix-editor/helix/pull/739))
1749+- Fix documentation popups sometimes not getting fully highlighted ([`066367c`](https://github.com/helix-editor/helix/commit/066367c))
1750+- Refactor apply_workspace_edit to remove assert ([`b02d872`](https://github.com/helix-editor/helix/commit/b02d872))
1751+- Wrap around the top of the picker menu when scrolling ([`c7d6e44`](https://github.com/helix-editor/helix/commit/c7d6e44))
1752+- Don't allow closing the last split if there's unsaved changes ([`3ff5b00`](https://github.com/helix-editor/helix/commit/3ff5b00))
1753+- Indentation used different default on hx vs hx new_file.txt ([`c913bad`](https://github.com/helix-editor/helix/commit/c913bad))
1754+
1755+# 0.4.1 (2021-08-14)
1756+
1757+A minor release that includes:
1758+
1759+- A fix for rendering glitches that would occur after editing with multiple selections.
1760+- CI fix for grammars not being cross-compiled for aarch64
1761+
1762+# 0.4.0 (2021-08-13)
1763+
1764+A big shout out to all the contributors! We had 28 contributors in this release.
1765+
1766+Two months have passed, so this is another big release. A big thank you to all
1767+the contributors and package maintainers!
1768+
1769+Helix has popped up in [Arch, Manjaro, Nix, MacPorts and Parabola and Termux repositories](https://repology.org/project/helix/versions)!
1770+
1771+A [large scale refactor](https://github.com/helix-editor/helix/pull/376) landed that allows us to support zero width (empty)
1772+selections in the future as well as resolves many bugs and edge cases.
1773+
1774+- Multi-key remapping! Key binds now support much more complex usecases ([#454](https://github.com/helix-editor/helix/pull/454))
1775+- Pending keys are shown in the statusline ([#515](https://github.com/helix-editor/helix/pull/515))
1776+- Object selection / textobjects. `mi(` to select text inside parentheses ([#385](https://github.com/helix-editor/helix/pull/385))
1777+- Autoinfo: `whichkey`-like popups which show available sub-mode shortcuts ([#316](https://github.com/helix-editor/helix/pull/316))
1778+- Added WORD movements (W/B/E) ([#390](https://github.com/helix-editor/helix/pull/390))
1779+- Vertical selections (repeat selection above/below) ([#462](https://github.com/helix-editor/helix/pull/462))
1780+- Selection rotation via `(` and `)` ([`66a90130`](https://github.com/helix-editor/helix/commit/66a90130a5f99d769e9f6034025297f78ecaa3ec))
1781+- Selection contents rotation via `Alt-(` and `Alt-)` ([`02cba2a`](https://github.com/helix-editor/helix/commit/02cba2a7f403f48eccb18100fb751f7b42373dba))
1782+- Completion behavior improvements ([`f917b5a4`](https://github.com/helix-editor/helix/commit/f917b5a441ff3ae582358b6939ffbf889f4aa530), [`627b899`](https://github.com/helix-editor/helix/commit/627b89931576f7af86166ae8d5cbc55537877473))
1783+- Fixed a language server crash ([`385a6b5a`](https://github.com/helix-editor/helix/commit/385a6b5a1adddfc26e917982641530e1a7c7aa81))
1784+- Case change commands (`` ` ``, `~`, ``<a-`>``) ([#441](https://github.com/helix-editor/helix/pull/441))
1785+- File pickers (including goto) now provide a preview! ([#534](https://github.com/helix-editor/helix/pull/534))
1786+- Injection query support. Rust macro calls and embedded languages are now properly highlighted ([#430](https://github.com/helix-editor/helix/pull/430))
1787+- Formatting is now asynchronous, and the async job infrastructure has been improved ([#285](https://github.com/helix-editor/helix/pull/285))
1788+- Grammars are now compiled as separate shared libraries and loaded on-demand at runtime ([#432](https://github.com/helix-editor/helix/pull/432))
1789+- Code action support ([#478](https://github.com/helix-editor/helix/pull/478))
1790+- Mouse support ([#509](https://github.com/helix-editor/helix/pull/509), [#548](https://github.com/helix-editor/helix/pull/548))
1791+- Native Windows clipboard support ([#373](https://github.com/helix-editor/helix/pull/373))
1792+- Themes can now use color palettes ([#393](https://github.com/helix-editor/helix/pull/393))
1793+- `:reload` command ([#374](https://github.com/helix-editor/helix/pull/374))
1794+- Ctrl-z to suspend ([#464](https://github.com/helix-editor/helix/pull/464))
1795+- Language servers can now be configured with a custom JSON config ([#460](https://github.com/helix-editor/helix/pull/460))
1796+- Comment toggling now uses a language specific comment token ([#463](https://github.com/helix-editor/helix/pull/463))
1797+- Julia support ([#413](https://github.com/helix-editor/helix/pull/413))
1798+- Java support ([#448](https://github.com/helix-editor/helix/pull/448))
1799+- Prompts have an (in-memory) history ([`63e54e30`](https://github.com/helix-editor/helix/commit/63e54e30a74bb0d1d782877ddbbcf95f2817d061))
1800+
1801+# 0.3.0 (2021-06-27)
1802+
1803+A big shout out to all the contributors! We had 24 contributors in this release.
1804+
1805+Another big release.
1806+
1807+Highlights:
1808+
1809+- Indentation is now automatically detected from file heuristics. ([#245](https://github.com/helix-editor/helix/pull/245))
1810+- Support for other line endings (CRLF). Significantly improved Windows support. ([#224](https://github.com/helix-editor/helix/pull/224))
1811+- Encodings other than UTF-8 are now supported! ([#228](https://github.com/helix-editor/helix/pull/228))
1812+- Key bindings can now be configured via a `config.toml` file ([#268](https://github.com/helix-editor/helix/pull/268))
1813+- Theme can now be configured and changed at runtime. ([Please feel free to contribute more themes!](https://github.com/helix-editor/helix/tree/master/runtime/themes)) ([#267](https://github.com/helix-editor/helix/pull/267))
1814+- System clipboard yank/paste is now supported! ([#310](https://github.com/helix-editor/helix/pull/310))
1815+- Surround commands were implemented ([#320](https://github.com/helix-editor/helix/pull/320))
1816+
1817+Features:
1818+
1819+- File picker can now be repeatedly filtered ([#232](https://github.com/helix-editor/helix/pull/232))
1820+- LSP progress is now received and rendered as a spinner ([#234](https://github.com/helix-editor/helix/pull/234))
1821+- Current line number can now be themed ([#260](https://github.com/helix-editor/helix/pull/260))
1822+- Arrow keys & home/end now work in insert mode ([#305](https://github.com/helix-editor/helix/pull/305))
1823+- Cursors and selections can now be themed ([#325](https://github.com/helix-editor/helix/pull/325))
1824+- Language servers are now gracefully shut down before `hx` exits ([#287](https://github.com/helix-editor/helix/pull/287))
1825+- `:show-directory`/`:change-directory` ([#335](https://github.com/helix-editor/helix/pull/335))
1826+- File picker is now sorted by access time (before filtering) ([#336](https://github.com/helix-editor/helix/pull/336))
1827+- Code is being migrated from helix-term to helix-view (prerequisite for
1828+ alternative frontends) ([#366](https://github.com/helix-editor/helix/pull/366))
1829+- `x` and `X` merged
1830+ ([`f41688d9`](https://github.com/helix-editor/helix/commit/f41688d960ef89c29c4a51c872b8406fb8f81a85))
1831+
1832+Fixes:
1833+
1834+- The IME popup is now correctly positioned ([#273](https://github.com/helix-editor/helix/pull/273))
1835+- A bunch of bugs regarding `o`/`O` behavior ([#281](https://github.com/helix-editor/helix/pull/281))
1836+- `~` expansion now works in file completion ([#284](https://github.com/helix-editor/helix/pull/284))
1837+- Several UI related overflow crashes ([#318](https://github.com/helix-editor/helix/pull/318))
1838+- Fix a test failure occurring only on `test --release` ([`4f108ab1`](https://github.com/helix-editor/helix/commit/4f108ab1b2197809506bd7305ad903a3525eabfa))
1839+- Prompts now support unicode input ([#295](https://github.com/helix-editor/helix/pull/295))
1840+- Completion documentation no longer overlaps the popup ([#322](https://github.com/helix-editor/helix/pull/322))
1841+- Fix a crash when trying to select `^` ([`9c534614`](https://github.com/helix-editor/helix/commit/9c53461429a3e72e3b1fb87d7ca490e168d7dee2))
1842+- Prompt completions are now paginated ([`39dc09e6`](https://github.com/helix-editor/helix/commit/39dc09e6c4172299bc79de4c1c52288d3f624bd7))
1843+- Goto did not work on Windows ([`503ca112`](https://github.com/helix-editor/helix/commit/503ca112ae57ebdf3ea323baf8940346204b46d2))
1844+
1845+# 0.2.1
1846+
1847+Includes a fix where wq/wqa could exit before file saving completed.
1848+
1849+# 0.2.0
1850+
1851+A big shout out to all the contributors! We had 18 contributors in this release.
1852+
1853+Enough has changed to bump the version. We're skipping 0.1.x because
1854+previously the CLI would always report version as 0.1.0, and we'd like
1855+to distinguish it in bug reports..
1856+
1857+- The `runtime/` directory is now properly detected on binary releases and
1858+ on cargo run. `~/.config/helix/runtime` can also be used.
1859+- Registers can now be selected via " (for example, `"ay`)
1860+- Support for Nix files was added
1861+- Movement is now fully tested and matches Kakoune implementation
1862+- A per-file LSP symbol picker was added to space+s
1863+- Selection can be replaced with yanked text via R
1864+
1865+- `1g` now correctly goes to line 1
1866+- `ctrl-i` now correctly jumps backwards in history
1867+- A small memory leak was fixed, where we tried to reuse tree-sitter
1868+ query cursors, but always allocated a new one
1869+- Auto-formatting is now only on for certain languages
1870+- The root directory is now provided in LSP initialization, fixing
1871+ certain language servers (typescript)
1872+- LSP failing to start no longer panics
1873+- Elixir language queries were fixed
1874+
1875+# 0.0.10
1876+
1877+Keymaps:
1878+- Add mappings to jump to diagnostics
1879+- Add gt/gm/gb mappings to jump to top/middle/bottom of screen
1880+- ^ and $ are now gh, gl
1881+
1882+- The runtime/ can now optionally be embedded in the binary
1883+- Haskell syntax added
1884+- Window mode (ctrl-w) added
1885+- Show matching bracket (Vim's matchbrackets)
1886+- Themes now support style modifiers
1887+- First user contributed theme
1888+- Create a document if it doesn't exist yet on save
1889+- Detect language on a new file on save
1890+
1891+- Panic fixes, lots of them
Cargo.lock+2093−723
@@ -1,1281 +1,2651 @@
1# This file is automatically @generated by Cargo.
1# This file is automatically @generated by Cargo.
2# It is not intended for manual editing.
2# It is not intended for manual editing.
3+version = 3
4+
3[[package]]
5[[package]]
4−name = "aho-corasick"
6+name = "addr2line"
5−version = "0.7.15"
7+version = "0.20.0"
6source = "registry+https://github.com/rust-lang/crates.io-index"
8source = "registry+https://github.com/rust-lang/crates.io-index"
7−checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
9+checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
8dependencies = [
10dependencies = [
9− "memchr",
11+ "gimli",
10]
12]
11
13
12[[package]]
14[[package]]
13−name = "anyhow"
15+name = "adler"
14−version = "1.0.34"
16+version = "1.0.2"
15source = "registry+https://github.com/rust-lang/crates.io-index"
17source = "registry+https://github.com/rust-lang/crates.io-index"
16−checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
18+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
17
19
18[[package]]
20[[package]]
19−name = "arrayref"
21+name = "ahash"
20−version = "0.3.6"
22+version = "0.8.6"
21source = "registry+https://github.com/rust-lang/crates.io-index"
23source = "registry+https://github.com/rust-lang/crates.io-index"
22−checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
24+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
25+dependencies = [
26+ "cfg-if",
27+ "getrandom",
28+ "once_cell",
29+ "version_check",
30+ "zerocopy",
31+]
23
32
24[[package]]
33[[package]]
25−name = "arrayvec"
34+name = "aho-corasick"
26−version = "0.5.2"
35+version = "1.1.2"
27source = "registry+https://github.com/rust-lang/crates.io-index"
36source = "registry+https://github.com/rust-lang/crates.io-index"
28−checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
37+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
38+dependencies = [
39+ "memchr",
40+]
29
41
30[[package]]
42[[package]]
31−name = "async-channel"
43+name = "allocator-api2"
32−version = "1.5.1"
44+version = "0.2.14"
33source = "registry+https://github.com/rust-lang/crates.io-index"
45source = "registry+https://github.com/rust-lang/crates.io-index"
34−checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9"
46+checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e"
35−dependencies = [
36− "concurrent-queue",
37− "event-listener",
38− "futures-core",
39−]
40
47
41[[package]]
48[[package]]
42−name = "async-executor"
49+name = "android-tzdata"
43−version = "1.4.0"
50+version = "0.1.1"
44source = "registry+https://github.com/rust-lang/crates.io-index"
51source = "registry+https://github.com/rust-lang/crates.io-index"
45−checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
52+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
46−dependencies = [
47− "async-task",
48− "concurrent-queue",
49− "fastrand",
50− "futures-lite",
51− "once_cell",
52− "vec-arena",
53−]
54
53
55[[package]]
54[[package]]
56−name = "async-fs"
55+name = "android_system_properties"
57−version = "1.5.0"
56+version = "0.1.5"
58source = "registry+https://github.com/rust-lang/crates.io-index"
57source = "registry+https://github.com/rust-lang/crates.io-index"
59−checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2"
58+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
60dependencies = [
59dependencies = [
61− "async-lock",
60+ "libc",
62− "blocking",
63− "futures-lite",
64]
61]
65
62
66[[package]]
63[[package]]
67−name = "async-io"
64+name = "anyhow"
65+version = "1.0.76"
66+source = "registry+https://github.com/rust-lang/crates.io-index"
67+checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
68+
69+[[package]]
70+name = "arc-swap"
68−version = "1.3.1"
71+version = "1.6.0"
72+source = "registry+https://github.com/rust-lang/crates.io-index"
73+checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
74+
75+[[package]]
76+name = "autocfg"
77+version = "1.1.0"
78+source = "registry+https://github.com/rust-lang/crates.io-index"
79+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
80+
81+[[package]]
82+name = "backtrace"
83+version = "0.3.68"
69source = "registry+https://github.com/rust-lang/crates.io-index"
84source = "registry+https://github.com/rust-lang/crates.io-index"
70−checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd"
85+checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
71dependencies = [
86dependencies = [
72− "concurrent-queue",
87+ "addr2line",
73− "fastrand",
88+ "cc",
74− "futures-lite",
89+ "cfg-if",
75 "libc",
90 "libc",
76− "log",
91+ "miniz_oxide",
77− "nb-connect",
92+ "object",
78− "once_cell",
93+ "rustc-demangle",
79− "parking",
80− "polling",
81− "vec-arena",
82− "waker-fn",
83− "winapi",
84]
94]
85
95
86[[package]]
96[[package]]
87−name = "async-lock"
97+name = "bitflags"
88−version = "2.3.0"
98+version = "1.3.2"
89source = "registry+https://github.com/rust-lang/crates.io-index"
99source = "registry+https://github.com/rust-lang/crates.io-index"
90−checksum = "1996609732bde4a9988bc42125f55f2af5f3c36370e27c778d5191a4a1b63bfb"
100+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
91−dependencies = [
101+
92− "event-listener",
102+[[package]]
93−]
103+name = "bitflags"
104+version = "2.4.1"
105+source = "registry+https://github.com/rust-lang/crates.io-index"
106+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
94
107
95[[package]]
108[[package]]
96−name = "async-net"
109+name = "bstr"
97−version = "1.5.0"
110+version = "1.8.0"
98source = "registry+https://github.com/rust-lang/crates.io-index"
111source = "registry+https://github.com/rust-lang/crates.io-index"
99−checksum = "06de475c85affe184648202401d7622afb32f0f74e02192857d0201a16defbe5"
112+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
100dependencies = [
113dependencies = [
101− "async-io",
114+ "memchr",
102− "blocking",
115+ "regex-automata",
103− "fastrand",
116+ "serde",
104− "futures-lite",
105]
117]
106
118
107[[package]]
119[[package]]
108−name = "async-process"
120+name = "btoi"
109−version = "1.0.1"
121+version = "0.4.3"
110source = "registry+https://github.com/rust-lang/crates.io-index"
122source = "registry+https://github.com/rust-lang/crates.io-index"
111−checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda"
123+checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad"
112dependencies = [
124dependencies = [
113− "async-io",
125+ "num-traits",
114− "blocking",
115− "cfg-if 0.1.10",
116− "event-listener",
117− "futures-lite",
118− "once_cell",
119− "signal-hook",
120− "winapi",
121]
126]
122
127
123[[package]]
128[[package]]
124−name = "async-task"
129+name = "bumpalo"
125−version = "4.0.3"
130+version = "3.12.0"
126source = "registry+https://github.com/rust-lang/crates.io-index"
131source = "registry+https://github.com/rust-lang/crates.io-index"
127−checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
132+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
128
133
129[[package]]
134[[package]]
130−name = "atomic-waker"
135+name = "bytes"
131−version = "1.0.0"
136+version = "1.4.0"
132source = "registry+https://github.com/rust-lang/crates.io-index"
137source = "registry+https://github.com/rust-lang/crates.io-index"
133−checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
138+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
134
139
135[[package]]
140[[package]]
136−name = "autocfg"
141+name = "cassowary"
137−version = "1.0.1"
142+version = "0.3.0"
138source = "registry+https://github.com/rust-lang/crates.io-index"
143source = "registry+https://github.com/rust-lang/crates.io-index"
139−checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
144+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
140
145
141[[package]]
146[[package]]
142−name = "base64"
147+name = "cc"
143−version = "0.12.3"
148+version = "1.0.84"
144source = "registry+https://github.com/rust-lang/crates.io-index"
149source = "registry+https://github.com/rust-lang/crates.io-index"
145−checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
150+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
151+dependencies = [
152+ "libc",
153+]
146
154
147[[package]]
155[[package]]
148−name = "base64"
156+name = "cfg-if"
149−version = "0.13.0"
157+version = "1.0.0"
150source = "registry+https://github.com/rust-lang/crates.io-index"
158source = "registry+https://github.com/rust-lang/crates.io-index"
151−checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
159+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
152
160
153[[package]]
161[[package]]
154−name = "bitflags"
162+name = "chardetng"
155−version = "1.2.1"
163+version = "0.1.17"
156source = "registry+https://github.com/rust-lang/crates.io-index"
164source = "registry+https://github.com/rust-lang/crates.io-index"
157−checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
165+checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea"
166+dependencies = [
167+ "cfg-if",
168+ "encoding_rs",
169+ "memchr",
170+]
158
171
159[[package]]
172[[package]]
160−name = "blake2b_simd"
173+name = "chrono"
161−version = "0.5.11"
174+version = "0.4.31"
162source = "registry+https://github.com/rust-lang/crates.io-index"
175source = "registry+https://github.com/rust-lang/crates.io-index"
163−checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
176+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
164dependencies = [
177dependencies = [
165− "arrayref",
178+ "android-tzdata",
166− "arrayvec",
179+ "iana-time-zone",
167− "constant_time_eq",
180+ "num-traits",
181+ "windows-targets 0.48.0",
168]
182]
169
183
170[[package]]
184[[package]]
171−name = "blocking"
185+name = "clipboard-win"
172−version = "1.0.2"
186+version = "5.0.0"
173source = "registry+https://github.com/rust-lang/crates.io-index"
187source = "registry+https://github.com/rust-lang/crates.io-index"
174−checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
188+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
175dependencies = [
189dependencies = [
176− "async-channel",
190+ "error-code",
177− "async-task",
178− "atomic-waker",
179− "fastrand",
180− "futures-lite",
181− "once_cell",
182]
191]
183
192
184[[package]]
193[[package]]
185−name = "cache-padded"
194+name = "clru"
186−version = "1.1.1"
195+version = "0.6.1"
187source = "registry+https://github.com/rust-lang/crates.io-index"
196source = "registry+https://github.com/rust-lang/crates.io-index"
188−checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
197+checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807"
189
198
190[[package]]
199[[package]]
191−name = "cassowary"
200+name = "codespan-reporting"
192−version = "0.3.0"
201+version = "0.11.1"
193source = "registry+https://github.com/rust-lang/crates.io-index"
202source = "registry+https://github.com/rust-lang/crates.io-index"
194−checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
203+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
204+dependencies = [
205+ "termcolor",
206+ "unicode-width",
207+]
195
208
196[[package]]
209[[package]]
197−name = "cc"
210+name = "content_inspector"
198−version = "1.0.65"
211+version = "0.2.4"
199source = "registry+https://github.com/rust-lang/crates.io-index"
212source = "registry+https://github.com/rust-lang/crates.io-index"
200−checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
213+checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
201dependencies = [
214dependencies = [
202− "jobserver",
215+ "memchr",
203]
216]
204
217
205[[package]]
218[[package]]
206−name = "cfg-if"
219+name = "core-foundation-sys"
207−version = "0.1.10"
220+version = "0.8.4"
208source = "registry+https://github.com/rust-lang/crates.io-index"
221source = "registry+https://github.com/rust-lang/crates.io-index"
209−checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
222+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
210
223
211[[package]]
224[[package]]
212−name = "cfg-if"
225+name = "cov-mark"
213−version = "1.0.0"
226+version = "1.1.0"
214source = "registry+https://github.com/rust-lang/crates.io-index"
227source = "registry+https://github.com/rust-lang/crates.io-index"
215−checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
228+checksum = "9ffa3d3e0138386cd4361f63537765cac7ee40698028844635a54495a92f67f3"
216
229
217[[package]]
230[[package]]
218−name = "chrono"
231+name = "crc32fast"
219−version = "0.4.19"
232+version = "1.3.2"
220source = "registry+https://github.com/rust-lang/crates.io-index"
233source = "registry+https://github.com/rust-lang/crates.io-index"
221−checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
234+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
222dependencies = [
235dependencies = [
223− "libc",
236+ "cfg-if",
224− "num-integer",
225− "num-traits",
226− "time",
227− "winapi",
228]
237]
229
238
230[[package]]
239[[package]]
231−name = "clap"
240+name = "crossbeam-channel"
232−version = "3.0.0-beta.2"
241+version = "0.5.8"
233source = "registry+https://github.com/rust-lang/crates.io-index"
242source = "registry+https://github.com/rust-lang/crates.io-index"
234−checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
243+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
235dependencies = [
244dependencies = [
236− "bitflags",
245+ "cfg-if",
237− "indexmap",
246+ "crossbeam-utils",
238− "lazy_static",
239− "os_str_bytes",
240− "textwrap",
241− "unicode-width",
242− "vec_map",
243]
247]
244
248
245[[package]]
249[[package]]
246−name = "cloudabi"
250+name = "crossbeam-deque"
247−version = "0.1.0"
251+version = "0.8.3"
248source = "registry+https://github.com/rust-lang/crates.io-index"
252source = "registry+https://github.com/rust-lang/crates.io-index"
249−checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
253+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
250dependencies = [
254dependencies = [
251− "bitflags",
255+ "cfg-if",
256+ "crossbeam-epoch",
257+ "crossbeam-utils",
252]
258]
253
259
254[[package]]
260[[package]]
255−name = "concurrent-queue"
261+name = "crossbeam-epoch"
256−version = "1.2.2"
262+version = "0.9.15"
257source = "registry+https://github.com/rust-lang/crates.io-index"
263source = "registry+https://github.com/rust-lang/crates.io-index"
258−checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
264+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
259dependencies = [
265dependencies = [
260− "cache-padded",
266+ "autocfg",
267+ "cfg-if",
268+ "crossbeam-utils",
269+ "memoffset",
270+ "scopeguard",
261]
271]
262
272
263−[[package]]
264−name = "constant_time_eq"
265−version = "0.1.5"
266−source = "registry+https://github.com/rust-lang/crates.io-index"
267−checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
268−
269[[package]]
273[[package]]
270name = "crossbeam-utils"
274name = "crossbeam-utils"
271−version = "0.8.1"
275+version = "0.8.16"
272source = "registry+https://github.com/rust-lang/crates.io-index"
276source = "registry+https://github.com/rust-lang/crates.io-index"
273−checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
277+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
274dependencies = [
278dependencies = [
275− "autocfg",
276− "cfg-if 1.0.0",
279+ "cfg-if",
277− "lazy_static",
278]
280]
279
281
280[[package]]
282[[package]]
281name = "crossterm"
283name = "crossterm"
282−version = "0.18.2"
284+version = "0.27.0"
283source = "registry+https://github.com/rust-lang/crates.io-index"
285source = "registry+https://github.com/rust-lang/crates.io-index"
284−checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb"
286+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
285dependencies = [
287dependencies = [
286− "bitflags",
288+ "bitflags 2.4.1",
287 "crossterm_winapi",
289 "crossterm_winapi",
290+ "filedescriptor",
288 "futures-core",
291 "futures-core",
289− "lazy_static",
290 "libc",
292 "libc",
291 "mio",
293 "mio",
292 "parking_lot",
294 "parking_lot",
293 "signal-hook",
295 "signal-hook",
296+ "signal-hook-mio",
294 "winapi",
297 "winapi",
295]
298]
296
299
297[[package]]
300[[package]]
298name = "crossterm_winapi"
301name = "crossterm_winapi"
299−version = "0.6.2"
302+version = "0.9.1"
300source = "registry+https://github.com/rust-lang/crates.io-index"
303source = "registry+https://github.com/rust-lang/crates.io-index"
301−checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db"
304+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
302dependencies = [
305dependencies = [
303 "winapi",
306 "winapi",
304]
307]
305
308
306[[package]]
309[[package]]
307−name = "dirs"
310+name = "cxx"
308−version = "2.0.2"
311+version = "1.0.94"
309source = "registry+https://github.com/rust-lang/crates.io-index"
312source = "registry+https://github.com/rust-lang/crates.io-index"
310−checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
313+checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
311dependencies = [
314dependencies = [
312− "cfg-if 0.1.10",
315+ "cc",
313− "dirs-sys",
316+ "cxxbridge-flags",
317+ "cxxbridge-macro",
318+ "link-cplusplus",
314]
319]
315
320
316[[package]]
321[[package]]
317−name = "dirs-sys"
322+name = "cxx-build"
318−version = "0.3.5"
323+version = "1.0.94"
319source = "registry+https://github.com/rust-lang/crates.io-index"
324source = "registry+https://github.com/rust-lang/crates.io-index"
320−checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
325+checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
321dependencies = [
326dependencies = [
322− "libc",
327+ "cc",
323− "redox_users",
328+ "codespan-reporting",
324− "winapi",
329+ "once_cell",
330+ "proc-macro2",
331+ "quote",
332+ "scratch",
333+ "syn 2.0.38",
325]
334]
326
335
327[[package]]
336[[package]]
328−name = "event-listener"
337+name = "cxxbridge-flags"
329−version = "2.5.1"
338+version = "1.0.94"
330source = "registry+https://github.com/rust-lang/crates.io-index"
339source = "registry+https://github.com/rust-lang/crates.io-index"
331−checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
340+checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
332
341
333[[package]]
342[[package]]
334−name = "fastrand"
343+name = "cxxbridge-macro"
335−version = "1.4.0"
344+version = "1.0.94"
336source = "registry+https://github.com/rust-lang/crates.io-index"
345source = "registry+https://github.com/rust-lang/crates.io-index"
337−checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
346+checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
338dependencies = [
347dependencies = [
339− "instant",
348+ "proc-macro2",
349+ "quote",
350+ "syn 2.0.38",
340]
351]
341
352
342[[package]]
353[[package]]
343−name = "fern"
354+name = "dunce"
344−version = "0.6.0"
355+version = "1.0.4"
345source = "registry+https://github.com/rust-lang/crates.io-index"
356source = "registry+https://github.com/rust-lang/crates.io-index"
346−checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065"
357+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
358+
359+[[package]]
360+name = "either"
361+version = "1.8.1"
362+source = "registry+https://github.com/rust-lang/crates.io-index"
363+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
364+
365+[[package]]
366+name = "encoding_rs"
367+version = "0.8.33"
368+source = "registry+https://github.com/rust-lang/crates.io-index"
369+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
347dependencies = [
370dependencies = [
348− "log",
371+ "cfg-if",
349]
372]
350
373
351[[package]]
374[[package]]
352−name = "form_urlencoded"
375+name = "encoding_rs_io"
376+version = "0.1.7"
377+source = "registry+https://github.com/rust-lang/crates.io-index"
378+checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
379+dependencies = [
380+ "encoding_rs",
381+]
382+
383+[[package]]
384+name = "equivalent"
353version = "1.0.0"
385version = "1.0.0"
354source = "registry+https://github.com/rust-lang/crates.io-index"
386source = "registry+https://github.com/rust-lang/crates.io-index"
355−checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
387+checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
388+
389+[[package]]
390+name = "errno"
391+version = "0.3.8"
392+source = "registry+https://github.com/rust-lang/crates.io-index"
393+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
356dependencies = [
394dependencies = [
357− "matches",
395+ "libc",
358− "percent-encoding",
396+ "windows-sys 0.52.0",
359]
397]
360
398
361[[package]]
399[[package]]
362−name = "futf"
400+name = "error-code"
363−version = "0.1.4"
401+version = "3.0.0"
402+source = "registry+https://github.com/rust-lang/crates.io-index"
403+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
404+
405+[[package]]
406+name = "etcetera"
407+version = "0.8.0"
364source = "registry+https://github.com/rust-lang/crates.io-index"
408source = "registry+https://github.com/rust-lang/crates.io-index"
365−checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
409+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
366dependencies = [
410dependencies = [
367− "mac",
411+ "cfg-if",
368− "new_debug_unreachable",
412+ "home",
413+ "windows-sys 0.48.0",
369]
414]
370
415
371[[package]]
416[[package]]
372−name = "futures"
417+name = "faster-hex"
373−version = "0.1.30"
418+version = "0.9.0"
374source = "registry+https://github.com/rust-lang/crates.io-index"
419source = "registry+https://github.com/rust-lang/crates.io-index"
375−checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed"
420+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
421+dependencies = [
422+ "serde",
423+]
376
424
377[[package]]
425[[package]]
378−name = "futures-core"
426+name = "fastrand"
379−version = "0.3.8"
427+version = "2.0.0"
380source = "registry+https://github.com/rust-lang/crates.io-index"
428source = "registry+https://github.com/rust-lang/crates.io-index"
381−checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
429+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
382
430
383[[package]]
431[[package]]
384−name = "futures-io"
432+name = "fern"
385−version = "0.3.8"
433+version = "0.6.2"
386source = "registry+https://github.com/rust-lang/crates.io-index"
434source = "registry+https://github.com/rust-lang/crates.io-index"
387−checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
435+checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
436+dependencies = [
437+ "log",
438+]
388
439
389[[package]]
440[[package]]
390−name = "futures-lite"
441+name = "filedescriptor"
391−version = "1.11.2"
442+version = "0.8.2"
392source = "registry+https://github.com/rust-lang/crates.io-index"
443source = "registry+https://github.com/rust-lang/crates.io-index"
393−checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658"
444+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
394dependencies = [
445dependencies = [
395− "fastrand",
446+ "libc",
396− "futures-core",
447+ "thiserror",
397− "futures-io",
448+ "winapi",
398− "memchr",
399− "parking",
400− "pin-project-lite",
401− "waker-fn",
402]
449]
403
450
404[[package]]
451[[package]]
405−name = "futures-macro"
452+name = "flate2"
406−version = "0.3.8"
453+version = "1.0.27"
407source = "registry+https://github.com/rust-lang/crates.io-index"
454source = "registry+https://github.com/rust-lang/crates.io-index"
408−checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
455+checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
409dependencies = [
456dependencies = [
410− "proc-macro-hack",
457+ "crc32fast",
411− "proc-macro2",
458+ "miniz_oxide",
412− "quote",
413− "syn",
414]
459]
415
460
416[[package]]
461[[package]]
417−name = "futures-task"
462+name = "form_urlencoded"
418−version = "0.3.8"
463+version = "1.2.1"
419source = "registry+https://github.com/rust-lang/crates.io-index"
464source = "registry+https://github.com/rust-lang/crates.io-index"
420−checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
465+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
421dependencies = [
466dependencies = [
422− "once_cell",
467+ "percent-encoding",
468+]
469+
470+[[package]]
471+name = "futures-core"
472+version = "0.3.30"
473+source = "registry+https://github.com/rust-lang/crates.io-index"
474+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
475+
476+[[package]]
477+name = "futures-executor"
478+version = "0.3.29"
479+source = "registry+https://github.com/rust-lang/crates.io-index"
480+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
481+dependencies = [
482+ "futures-core",
483+ "futures-task",
484+ "futures-util",
423]
485]
424
486
487+[[package]]
488+name = "futures-task"
489+version = "0.3.30"
490+source = "registry+https://github.com/rust-lang/crates.io-index"
491+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
492+
425[[package]]
493[[package]]
426name = "futures-util"
494name = "futures-util"
427−version = "0.3.8"
495+version = "0.3.30"
428source = "registry+https://github.com/rust-lang/crates.io-index"
496source = "registry+https://github.com/rust-lang/crates.io-index"
429−checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
497+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
430dependencies = [
498dependencies = [
431 "futures-core",
499 "futures-core",
432− "futures-macro",
433 "futures-task",
500 "futures-task",
434− "pin-project",
501+ "pin-project-lite",
435 "pin-utils",
502 "pin-utils",
436− "proc-macro-hack",
437− "proc-macro-nested",
438 "slab",
503 "slab",
439]
504]
440
505
441[[package]]
506[[package]]
442name = "getrandom"
507name = "getrandom"
443−version = "0.1.15"
508+version = "0.2.9"
444source = "registry+https://github.com/rust-lang/crates.io-index"
509source = "registry+https://github.com/rust-lang/crates.io-index"
445−checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
510+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
446dependencies = [
511dependencies = [
447− "cfg-if 0.1.10",
512+ "cfg-if",
448 "libc",
513 "libc",
449− "wasi 0.9.0+wasi-snapshot-preview1",
514+ "wasi",
515+]
516+
517+[[package]]
518+name = "gimli"
519+version = "0.27.3"
520+source = "registry+https://github.com/rust-lang/crates.io-index"
521+checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
522+
523+[[package]]
524+name = "gix"
525+version = "0.57.0"
526+source = "registry+https://github.com/rust-lang/crates.io-index"
527+checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
528+dependencies = [
529+ "gix-actor",
530+ "gix-commitgraph",
531+ "gix-config",
532+ "gix-date",
533+ "gix-diff",
534+ "gix-discover",
535+ "gix-features",
536+ "gix-fs",
537+ "gix-glob",
538+ "gix-hash",
539+ "gix-hashtable",
540+ "gix-lock",
541+ "gix-macros",
542+ "gix-object",
543+ "gix-odb",
544+ "gix-pack",
545+ "gix-path",
546+ "gix-ref",
547+ "gix-refspec",
548+ "gix-revision",
549+ "gix-revwalk",
550+ "gix-sec",
551+ "gix-tempfile",
552+ "gix-trace",
553+ "gix-traverse",
554+ "gix-url",
555+ "gix-utils",
556+ "gix-validate",
557+ "once_cell",
558+ "parking_lot",
559+ "smallvec",
560+ "thiserror",
561+ "unicode-normalization",
450]
562]
451
563
452[[package]]
564[[package]]
453−name = "glob"
565+name = "gix-actor"
454−version = "0.3.0"
566+version = "0.29.0"
455source = "registry+https://github.com/rust-lang/crates.io-index"
567source = "registry+https://github.com/rust-lang/crates.io-index"
456−checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
568+checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
569+dependencies = [
570+ "bstr",
571+ "btoi",
572+ "gix-date",
573+ "itoa",
574+ "thiserror",
575+ "winnow 0.5.28",
576+]
457
577
458[[package]]
578[[package]]
459−name = "hashbrown"
579+name = "gix-chunk"
460−version = "0.9.1"
580+version = "0.4.6"
461source = "registry+https://github.com/rust-lang/crates.io-index"
581source = "registry+https://github.com/rust-lang/crates.io-index"
462−checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
582+checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
463−
464−[[package]]
465−name = "helix-core"
466−version = "0.1.0"
467dependencies = [
583dependencies = [
468− "anyhow",
584+ "thiserror",
469− "helix-syntax",
470− "once_cell",
471− "regex",
472− "ropey",
473− "smallvec",
474− "tendril",
475− "tree-sitter",
476− "unicode-segmentation",
477− "unicode-width",
478]
585]
479
586
480[[package]]
587[[package]]
481−name = "helix-lsp"
588+name = "gix-commitgraph"
482−version = "0.1.0"
589+version = "0.23.0"
590+source = "registry+https://github.com/rust-lang/crates.io-index"
591+checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
483dependencies = [
592dependencies = [
484− "anyhow",
593+ "bstr",
485− "futures-util",
594+ "gix-chunk",
486− "glob",
595+ "gix-features",
487− "helix-core",
596+ "gix-hash",
488− "helix-view",
597+ "memmap2",
489− "jsonrpc-core",
490− "log",
491− "lsp-types",
492− "once_cell",
493− "pathdiff",
494− "serde",
495− "serde_json",
496− "shellexpand",
497− "smol",
498 "thiserror",
598 "thiserror",
499− "url",
500]
599]
501
600
502[[package]]
601[[package]]
503−name = "helix-syntax"
602+name = "gix-config"
504−version = "0.1.0"
603+version = "0.33.0"
604+source = "registry+https://github.com/rust-lang/crates.io-index"
605+checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
505dependencies = [
606dependencies = [
506− "cc",
607+ "bstr",
507− "tree-sitter",
608+ "gix-config-value",
609+ "gix-features",
610+ "gix-glob",
611+ "gix-path",
612+ "gix-ref",
613+ "gix-sec",
614+ "memchr",
615+ "once_cell",
616+ "smallvec",
617+ "thiserror",
618+ "unicode-bom",
619+ "winnow 0.5.28",
508]
620]
509
621
510[[package]]
622[[package]]
511−name = "helix-term"
623+name = "gix-config-value"
512−version = "0.1.0"
624+version = "0.14.2"
625+source = "registry+https://github.com/rust-lang/crates.io-index"
626+checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
513dependencies = [
627dependencies = [
514− "anyhow",
628+ "bitflags 2.4.1",
629+ "bstr",
630+ "gix-path",
631+ "libc",
632+ "thiserror",
633+]
634+
635+[[package]]
636+name = "gix-date"
637+version = "0.8.2"
638+source = "registry+https://github.com/rust-lang/crates.io-index"
639+checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
640+dependencies = [
641+ "bstr",
642+ "itoa",
643+ "thiserror",
644+ "time",
645+]
646+
647+[[package]]
648+name = "gix-diff"
649+version = "0.39.0"
650+source = "registry+https://github.com/rust-lang/crates.io-index"
651+checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
652+dependencies = [
653+ "bstr",
654+ "gix-hash",
655+ "gix-object",
656+ "thiserror",
657+]
658+
659+[[package]]
660+name = "gix-discover"
661+version = "0.28.0"
662+source = "registry+https://github.com/rust-lang/crates.io-index"
663+checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
664+dependencies = [
665+ "bstr",
666+ "dunce",
667+ "gix-hash",
668+ "gix-path",
669+ "gix-ref",
670+ "gix-sec",
671+ "thiserror",
672+]
673+
674+[[package]]
675+name = "gix-features"
676+version = "0.37.0"
677+source = "registry+https://github.com/rust-lang/crates.io-index"
678+checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
679+dependencies = [
680+ "crc32fast",
681+ "flate2",
682+ "gix-hash",
683+ "gix-trace",
684+ "libc",
685+ "once_cell",
686+ "prodash",
687+ "sha1_smol",
688+ "thiserror",
689+ "walkdir",
690+]
691+
692+[[package]]
693+name = "gix-fs"
694+version = "0.9.0"
695+source = "registry+https://github.com/rust-lang/crates.io-index"
696+checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
697+dependencies = [
698+ "gix-features",
699+]
700+
701+[[package]]
702+name = "gix-glob"
703+version = "0.15.0"
704+source = "registry+https://github.com/rust-lang/crates.io-index"
705+checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
706+dependencies = [
707+ "bitflags 2.4.1",
708+ "bstr",
709+ "gix-features",
710+ "gix-path",
711+]
712+
713+[[package]]
714+name = "gix-hash"
715+version = "0.14.0"
716+source = "registry+https://github.com/rust-lang/crates.io-index"
717+checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
718+dependencies = [
719+ "faster-hex",
720+ "thiserror",
721+]
722+
723+[[package]]
724+name = "gix-hashtable"
725+version = "0.5.0"
726+source = "registry+https://github.com/rust-lang/crates.io-index"
727+checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
728+dependencies = [
729+ "gix-hash",
730+ "hashbrown 0.14.3",
731+ "parking_lot",
732+]
733+
734+[[package]]
735+name = "gix-lock"
736+version = "12.0.0"
737+source = "registry+https://github.com/rust-lang/crates.io-index"
738+checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
739+dependencies = [
740+ "gix-tempfile",
741+ "gix-utils",
742+ "thiserror",
743+]
744+
745+[[package]]
746+name = "gix-macros"
747+version = "0.1.2"
748+source = "registry+https://github.com/rust-lang/crates.io-index"
749+checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
750+dependencies = [
751+ "proc-macro2",
752+ "quote",
753+ "syn 2.0.38",
754+]
755+
756+[[package]]
757+name = "gix-object"
758+version = "0.40.0"
759+source = "registry+https://github.com/rust-lang/crates.io-index"
760+checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
761+dependencies = [
762+ "bstr",
763+ "btoi",
764+ "gix-actor",
765+ "gix-date",
766+ "gix-features",
767+ "gix-hash",
768+ "gix-validate",
769+ "itoa",
770+ "smallvec",
771+ "thiserror",
772+ "winnow 0.5.28",
773+]
774+
775+[[package]]
776+name = "gix-odb"
777+version = "0.56.0"
778+source = "registry+https://github.com/rust-lang/crates.io-index"
779+checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
780+dependencies = [
781+ "arc-swap",
782+ "gix-date",
783+ "gix-features",
784+ "gix-hash",
785+ "gix-object",
786+ "gix-pack",
787+ "gix-path",
788+ "gix-quote",
789+ "parking_lot",
790+ "tempfile",
791+ "thiserror",
792+]
793+
794+[[package]]
795+name = "gix-pack"
796+version = "0.46.0"
797+source = "registry+https://github.com/rust-lang/crates.io-index"
798+checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
799+dependencies = [
800+ "clru",
801+ "gix-chunk",
802+ "gix-features",
803+ "gix-hash",
804+ "gix-hashtable",
805+ "gix-object",
806+ "gix-path",
807+ "gix-tempfile",
808+ "memmap2",
809+ "parking_lot",
810+ "smallvec",
811+ "thiserror",
812+]
813+
814+[[package]]
815+name = "gix-path"
816+version = "0.10.2"
817+source = "registry+https://github.com/rust-lang/crates.io-index"
818+checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
819+dependencies = [
820+ "bstr",
821+ "gix-trace",
822+ "home",
823+ "once_cell",
824+ "thiserror",
825+]
826+
827+[[package]]
828+name = "gix-quote"
829+version = "0.4.9"
830+source = "registry+https://github.com/rust-lang/crates.io-index"
831+checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
832+dependencies = [
833+ "bstr",
834+ "btoi",
835+ "thiserror",
836+]
837+
838+[[package]]
839+name = "gix-ref"
840+version = "0.40.0"
841+source = "registry+https://github.com/rust-lang/crates.io-index"
842+checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
843+dependencies = [
844+ "gix-actor",
845+ "gix-date",
846+ "gix-features",
847+ "gix-fs",
848+ "gix-hash",
849+ "gix-lock",
850+ "gix-object",
851+ "gix-path",
852+ "gix-tempfile",
853+ "gix-validate",
854+ "memmap2",
855+ "thiserror",
856+ "winnow 0.5.28",
857+]
858+
859+[[package]]
860+name = "gix-refspec"
861+version = "0.21.0"
862+source = "registry+https://github.com/rust-lang/crates.io-index"
863+checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
864+dependencies = [
865+ "bstr",
866+ "gix-hash",
867+ "gix-revision",
868+ "gix-validate",
869+ "smallvec",
870+ "thiserror",
871+]
872+
873+[[package]]
874+name = "gix-revision"
875+version = "0.25.0"
876+source = "registry+https://github.com/rust-lang/crates.io-index"
877+checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
878+dependencies = [
879+ "bstr",
880+ "gix-date",
881+ "gix-hash",
882+ "gix-hashtable",
883+ "gix-object",
884+ "gix-revwalk",
885+ "gix-trace",
886+ "thiserror",
887+]
888+
889+[[package]]
890+name = "gix-revwalk"
891+version = "0.11.0"
892+source = "registry+https://github.com/rust-lang/crates.io-index"
893+checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
894+dependencies = [
895+ "gix-commitgraph",
896+ "gix-date",
897+ "gix-hash",
898+ "gix-hashtable",
899+ "gix-object",
900+ "smallvec",
901+ "thiserror",
902+]
903+
904+[[package]]
905+name = "gix-sec"
906+version = "0.10.2"
907+source = "registry+https://github.com/rust-lang/crates.io-index"
908+checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
909+dependencies = [
910+ "bitflags 2.4.1",
911+ "gix-path",
912+ "libc",
913+ "windows 0.52.0",
914+]
915+
916+[[package]]
917+name = "gix-tempfile"
918+version = "12.0.0"
919+source = "registry+https://github.com/rust-lang/crates.io-index"
920+checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
921+dependencies = [
922+ "gix-fs",
923+ "libc",
924+ "once_cell",
925+ "parking_lot",
926+ "tempfile",
927+]
928+
929+[[package]]
930+name = "gix-trace"
931+version = "0.1.5"
932+source = "registry+https://github.com/rust-lang/crates.io-index"
933+checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
934+
935+[[package]]
936+name = "gix-traverse"
937+version = "0.36.0"
938+source = "registry+https://github.com/rust-lang/crates.io-index"
939+checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
940+dependencies = [
941+ "gix-commitgraph",
942+ "gix-date",
943+ "gix-hash",
944+ "gix-hashtable",
945+ "gix-object",
946+ "gix-revwalk",
947+ "smallvec",
948+ "thiserror",
949+]
950+
951+[[package]]
952+name = "gix-url"
953+version = "0.26.0"
954+source = "registry+https://github.com/rust-lang/crates.io-index"
955+checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
956+dependencies = [
957+ "bstr",
958+ "gix-features",
959+ "gix-path",
960+ "home",
961+ "thiserror",
962+ "url",
963+]
964+
965+[[package]]
966+name = "gix-utils"
967+version = "0.1.7"
968+source = "registry+https://github.com/rust-lang/crates.io-index"
969+checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
970+dependencies = [
971+ "fastrand",
972+]
973+
974+[[package]]
975+name = "gix-validate"
976+version = "0.8.2"
977+source = "registry+https://github.com/rust-lang/crates.io-index"
978+checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
979+dependencies = [
980+ "bstr",
981+ "thiserror",
982+]
983+
984+[[package]]
985+name = "globset"
986+version = "0.4.14"
987+source = "registry+https://github.com/rust-lang/crates.io-index"
988+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
989+dependencies = [
990+ "aho-corasick",
991+ "bstr",
992+ "log",
993+ "regex-automata",
994+ "regex-syntax",
995+]
996+
997+[[package]]
998+name = "grep-matcher"
999+version = "0.1.7"
1000+source = "registry+https://github.com/rust-lang/crates.io-index"
1001+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
1002+dependencies = [
1003+ "memchr",
1004+]
1005+
1006+[[package]]
1007+name = "grep-regex"
1008+version = "0.1.12"
1009+source = "registry+https://github.com/rust-lang/crates.io-index"
1010+checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
1011+dependencies = [
1012+ "bstr",
1013+ "grep-matcher",
1014+ "log",
1015+ "regex-automata",
1016+ "regex-syntax",
1017+]
1018+
1019+[[package]]
1020+name = "grep-searcher"
1021+version = "0.1.13"
1022+source = "registry+https://github.com/rust-lang/crates.io-index"
1023+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
1024+dependencies = [
1025+ "bstr",
1026+ "encoding_rs",
1027+ "encoding_rs_io",
1028+ "grep-matcher",
1029+ "log",
1030+ "memchr",
1031+ "memmap2",
1032+]
1033+
1034+[[package]]
1035+name = "hashbrown"
1036+version = "0.12.3"
1037+source = "registry+https://github.com/rust-lang/crates.io-index"
1038+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1039+
1040+[[package]]
1041+name = "hashbrown"
1042+version = "0.14.3"
1043+source = "registry+https://github.com/rust-lang/crates.io-index"
1044+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
1045+dependencies = [
1046+ "ahash",
1047+ "allocator-api2",
1048+]
1049+
1050+[[package]]
1051+name = "helix-core"
1052+version = "23.10.0"
1053+dependencies = [
1054+ "ahash",
1055+ "arc-swap",
1056+ "bitflags 2.4.1",
515 "chrono",
1057 "chrono",
516− "clap",
1058+ "dunce",
517− "crossterm",
1059+ "encoding_rs",
1060+ "etcetera",
1061+ "hashbrown 0.14.3",
1062+ "helix-loader",
1063+ "imara-diff",
1064+ "indoc",
1065+ "log",
1066+ "nucleo",
1067+ "once_cell",
1068+ "parking_lot",
1069+ "quickcheck",
1070+ "regex",
1071+ "ropey",
1072+ "serde",
1073+ "serde_json",
1074+ "slotmap",
1075+ "smallvec",
1076+ "smartstring",
1077+ "textwrap",
1078+ "toml",
1079+ "tree-sitter",
1080+ "unicode-general-category",
1081+ "unicode-segmentation",
1082+ "unicode-width",
1083+]
1084+
1085+[[package]]
1086+name = "helix-dap"
1087+version = "23.10.0"
1088+dependencies = [
1089+ "anyhow",
518 "fern",
1090 "fern",
519− "futures-util",
520 "helix-core",
1091 "helix-core",
521− "helix-lsp",
522− "helix-view",
523 "log",
1092 "log",
1093+ "serde",
1094+ "serde_json",
1095+ "thiserror",
1096+ "tokio",
1097+ "which",
1098+]
1099+
1100+[[package]]
1101+name = "helix-event"
1102+version = "23.10.0"
1103+dependencies = [
1104+ "parking_lot",
1105+ "tokio",
1106+]
1107+
1108+[[package]]
1109+name = "helix-loader"
1110+version = "23.10.0"
1111+dependencies = [
1112+ "anyhow",
1113+ "cc",
1114+ "dunce",
1115+ "etcetera",
1116+ "libloading",
1117+ "log",
1118+ "once_cell",
1119+ "serde",
1120+ "tempfile",
1121+ "threadpool",
1122+ "toml",
1123+ "tree-sitter",
1124+ "which",
1125+]
1126+
1127+[[package]]
1128+name = "helix-lsp"
1129+version = "23.10.0"
1130+dependencies = [
1131+ "anyhow",
1132+ "futures-executor",
1133+ "futures-util",
1134+ "globset",
1135+ "helix-core",
1136+ "helix-loader",
1137+ "helix-parsec",
1138+ "log",
1139+ "lsp-types",
1140+ "parking_lot",
1141+ "serde",
1142+ "serde_json",
1143+ "thiserror",
1144+ "tokio",
1145+ "tokio-stream",
1146+ "which",
1147+]
1148+
1149+[[package]]
1150+name = "helix-parsec"
1151+version = "23.10.0"
1152+
1153+[[package]]
1154+name = "helix-term"
1155+version = "23.10.0"
1156+dependencies = [
1157+ "anyhow",
1158+ "arc-swap",
1159+ "chrono",
1160+ "content_inspector",
1161+ "crossterm",
1162+ "fern",
1163+ "futures-util",
1164+ "grep-regex",
1165+ "grep-searcher",
1166+ "helix-core",
1167+ "helix-dap",
1168+ "helix-event",
1169+ "helix-loader",
1170+ "helix-lsp",
1171+ "helix-tui",
1172+ "helix-vcs",
1173+ "helix-view",
1174+ "ignore",
1175+ "indoc",
1176+ "libc",
1177+ "log",
1178+ "nucleo",
1179+ "once_cell",
1180+ "open",
1181+ "pulldown-cmark",
1182+ "serde",
1183+ "serde_json",
1184+ "signal-hook",
1185+ "signal-hook-tokio",
1186+ "smallvec",
1187+ "tempfile",
1188+ "tokio",
1189+ "tokio-stream",
1190+ "toml",
1191+ "url",
1192+ "which",
1193+]
1194+
1195+[[package]]
1196+name = "helix-tui"
1197+version = "23.10.0"
1198+dependencies = [
1199+ "bitflags 2.4.1",
1200+ "cassowary",
1201+ "crossterm",
1202+ "helix-core",
1203+ "helix-view",
1204+ "log",
1205+ "once_cell",
1206+ "serde",
1207+ "termini",
1208+ "unicode-segmentation",
1209+]
1210+
1211+[[package]]
1212+name = "helix-vcs"
1213+version = "23.10.0"
1214+dependencies = [
1215+ "anyhow",
1216+ "arc-swap",
1217+ "gix",
1218+ "helix-core",
1219+ "helix-event",
1220+ "imara-diff",
1221+ "log",
1222+ "parking_lot",
1223+ "tempfile",
1224+ "tokio",
1225+]
1226+
1227+[[package]]
1228+name = "helix-view"
1229+version = "23.10.0"
1230+dependencies = [
1231+ "anyhow",
1232+ "arc-swap",
1233+ "bitflags 2.4.1",
1234+ "chardetng",
1235+ "clipboard-win",
1236+ "crossterm",
1237+ "futures-util",
1238+ "helix-core",
1239+ "helix-dap",
1240+ "helix-event",
1241+ "helix-loader",
1242+ "helix-lsp",
1243+ "helix-tui",
1244+ "helix-vcs",
1245+ "libc",
1246+ "log",
1247+ "once_cell",
1248+ "parking_lot",
1249+ "rustix",
1250+ "serde",
1251+ "serde_json",
1252+ "slotmap",
1253+ "tokio",
1254+ "tokio-stream",
1255+ "toml",
1256+ "url",
1257+ "which",
1258+]
1259+
1260+[[package]]
1261+name = "hermit-abi"
1262+version = "0.2.6"
1263+source = "registry+https://github.com/rust-lang/crates.io-index"
1264+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
1265+dependencies = [
1266+ "libc",
1267+]
1268+
1269+[[package]]
1270+name = "home"
1271+version = "0.5.5"
1272+source = "registry+https://github.com/rust-lang/crates.io-index"
1273+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
1274+dependencies = [
1275+ "windows-sys 0.48.0",
1276+]
1277+
1278+[[package]]
1279+name = "iana-time-zone"
1280+version = "0.1.56"
1281+source = "registry+https://github.com/rust-lang/crates.io-index"
1282+checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
1283+dependencies = [
1284+ "android_system_properties",
1285+ "core-foundation-sys",
1286+ "iana-time-zone-haiku",
1287+ "js-sys",
1288+ "wasm-bindgen",
1289+ "windows 0.48.0",
1290+]
1291+
1292+[[package]]
1293+name = "iana-time-zone-haiku"
1294+version = "0.1.1"
1295+source = "registry+https://github.com/rust-lang/crates.io-index"
1296+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
1297+dependencies = [
1298+ "cxx",
1299+ "cxx-build",
1300+]
1301+
1302+[[package]]
1303+name = "idna"
1304+version = "0.5.0"
1305+source = "registry+https://github.com/rust-lang/crates.io-index"
1306+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
1307+dependencies = [
1308+ "unicode-bidi",
1309+ "unicode-normalization",
1310+]
1311+
1312+[[package]]
1313+name = "ignore"
1314+version = "0.4.21"
1315+source = "registry+https://github.com/rust-lang/crates.io-index"
1316+checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
1317+dependencies = [
1318+ "crossbeam-deque",
1319+ "globset",
1320+ "log",
1321+ "memchr",
1322+ "regex-automata",
1323+ "same-file",
1324+ "walkdir",
1325+ "winapi-util",
1326+]
1327+
1328+[[package]]
1329+name = "imara-diff"
1330+version = "0.1.5"
1331+source = "registry+https://github.com/rust-lang/crates.io-index"
1332+checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1333+dependencies = [
1334+ "ahash",
1335+ "hashbrown 0.12.3",
1336+]
1337+
1338+[[package]]
1339+name = "indexmap"
1340+version = "2.0.0"
1341+source = "registry+https://github.com/rust-lang/crates.io-index"
1342+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1343+dependencies = [
1344+ "equivalent",
1345+ "hashbrown 0.14.3",
1346+]
1347+
1348+[[package]]
1349+name = "indoc"
1350+version = "2.0.4"
1351+source = "registry+https://github.com/rust-lang/crates.io-index"
1352+checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
1353+
1354+[[package]]
1355+name = "is-docker"
1356+version = "0.2.0"
1357+source = "registry+https://github.com/rust-lang/crates.io-index"
1358+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1359+dependencies = [
1360+ "once_cell",
1361+]
1362+
1363+[[package]]
1364+name = "is-wsl"
1365+version = "0.4.0"
1366+source = "registry+https://github.com/rust-lang/crates.io-index"
1367+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1368+dependencies = [
1369+ "is-docker",
1370+ "once_cell",
1371+]
1372+
1373+[[package]]
1374+name = "itoa"
1375+version = "1.0.6"
1376+source = "registry+https://github.com/rust-lang/crates.io-index"
1377+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
1378+
1379+[[package]]
1380+name = "js-sys"
1381+version = "0.3.61"
1382+source = "registry+https://github.com/rust-lang/crates.io-index"
1383+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
1384+dependencies = [
1385+ "wasm-bindgen",
1386+]
1387+
1388+[[package]]
1389+name = "libc"
1390+version = "0.2.151"
1391+source = "registry+https://github.com/rust-lang/crates.io-index"
1392+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
1393+
1394+[[package]]
1395+name = "libloading"
1396+version = "0.8.1"
1397+source = "registry+https://github.com/rust-lang/crates.io-index"
1398+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
1399+dependencies = [
1400+ "cfg-if",
1401+ "windows-sys 0.48.0",
1402+]
1403+
1404+[[package]]
1405+name = "link-cplusplus"
1406+version = "1.0.8"
1407+source = "registry+https://github.com/rust-lang/crates.io-index"
1408+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
1409+dependencies = [
1410+ "cc",
1411+]
1412+
1413+[[package]]
1414+name = "linux-raw-sys"
1415+version = "0.4.11"
1416+source = "registry+https://github.com/rust-lang/crates.io-index"
1417+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
1418+
1419+[[package]]
1420+name = "lock_api"
1421+version = "0.4.9"
1422+source = "registry+https://github.com/rust-lang/crates.io-index"
1423+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
1424+dependencies = [
1425+ "autocfg",
1426+ "scopeguard",
1427+]
1428+
1429+[[package]]
1430+name = "log"
1431+version = "0.4.20"
1432+source = "registry+https://github.com/rust-lang/crates.io-index"
1433+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
1434+
1435+[[package]]
1436+name = "lsp-types"
1437+version = "0.95.0"
1438+source = "registry+https://github.com/rust-lang/crates.io-index"
1439+checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
1440+dependencies = [
1441+ "bitflags 1.3.2",
1442+ "serde",
1443+ "serde_json",
1444+ "serde_repr",
1445+ "url",
1446+]
1447+
1448+[[package]]
1449+name = "memchr"
1450+version = "2.6.3"
1451+source = "registry+https://github.com/rust-lang/crates.io-index"
1452+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
1453+
1454+[[package]]
1455+name = "memmap2"
1456+version = "0.9.0"
1457+source = "registry+https://github.com/rust-lang/crates.io-index"
1458+checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
1459+dependencies = [
1460+ "libc",
1461+]
1462+
1463+[[package]]
1464+name = "memoffset"
1465+version = "0.9.0"
1466+source = "registry+https://github.com/rust-lang/crates.io-index"
1467+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1468+dependencies = [
1469+ "autocfg",
1470+]
1471+
1472+[[package]]
1473+name = "miniz_oxide"
1474+version = "0.7.1"
1475+source = "registry+https://github.com/rust-lang/crates.io-index"
1476+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1477+dependencies = [
1478+ "adler",
1479+]
1480+
1481+[[package]]
1482+name = "mio"
1483+version = "0.8.9"
1484+source = "registry+https://github.com/rust-lang/crates.io-index"
1485+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
1486+dependencies = [
1487+ "libc",
1488+ "log",
1489+ "wasi",
1490+ "windows-sys 0.48.0",
1491+]
1492+
1493+[[package]]
1494+name = "nucleo"
1495+version = "0.2.1"
1496+source = "registry+https://github.com/rust-lang/crates.io-index"
1497+checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
1498+dependencies = [
1499+ "nucleo-matcher",
1500+ "parking_lot",
1501+ "rayon",
1502+]
1503+
1504+[[package]]
1505+name = "nucleo-matcher"
1506+version = "0.2.0"
1507+source = "registry+https://github.com/rust-lang/crates.io-index"
1508+checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
1509+dependencies = [
1510+ "cov-mark",
1511+ "memchr",
1512+ "unicode-segmentation",
1513+]
1514+
1515+[[package]]
1516+name = "num-traits"
1517+version = "0.2.15"
1518+source = "registry+https://github.com/rust-lang/crates.io-index"
1519+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
1520+dependencies = [
1521+ "autocfg",
1522+]
1523+
1524+[[package]]
1525+name = "num_cpus"
1526+version = "1.15.0"
1527+source = "registry+https://github.com/rust-lang/crates.io-index"
1528+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1529+dependencies = [
1530+ "hermit-abi",
1531+ "libc",
1532+]
1533+
1534+[[package]]
1535+name = "num_threads"
1536+version = "0.1.6"
1537+source = "registry+https://github.com/rust-lang/crates.io-index"
1538+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
1539+dependencies = [
1540+ "libc",
1541+]
1542+
1543+[[package]]
1544+name = "object"
1545+version = "0.31.1"
1546+source = "registry+https://github.com/rust-lang/crates.io-index"
1547+checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
1548+dependencies = [
1549+ "memchr",
1550+]
1551+
1552+[[package]]
1553+name = "once_cell"
1554+version = "1.19.0"
1555+source = "registry+https://github.com/rust-lang/crates.io-index"
1556+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1557+
1558+[[package]]
1559+name = "open"
1560+version = "5.0.1"
1561+source = "registry+https://github.com/rust-lang/crates.io-index"
1562+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563+dependencies = [
1564+ "is-wsl",
1565+ "libc",
1566+ "pathdiff",
1567+]
1568+
1569+[[package]]
1570+name = "parking_lot"
1571+version = "0.12.1"
1572+source = "registry+https://github.com/rust-lang/crates.io-index"
1573+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
1574+dependencies = [
1575+ "lock_api",
1576+ "parking_lot_core",
1577+]
1578+
1579+[[package]]
1580+name = "parking_lot_core"
1581+version = "0.9.7"
1582+source = "registry+https://github.com/rust-lang/crates.io-index"
1583+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
1584+dependencies = [
1585+ "cfg-if",
1586+ "libc",
1587+ "redox_syscall 0.2.16",
1588+ "smallvec",
1589+ "windows-sys 0.45.0",
1590+]
1591+
1592+[[package]]
1593+name = "pathdiff"
1594+version = "0.2.1"
1595+source = "registry+https://github.com/rust-lang/crates.io-index"
1596+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
1597+
1598+[[package]]
1599+name = "percent-encoding"
1600+version = "2.3.1"
1601+source = "registry+https://github.com/rust-lang/crates.io-index"
1602+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1603+
1604+[[package]]
1605+name = "pin-project-lite"
1606+version = "0.2.12"
1607+source = "registry+https://github.com/rust-lang/crates.io-index"
1608+checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
1609+
1610+[[package]]
1611+name = "pin-utils"
1612+version = "0.1.0"
1613+source = "registry+https://github.com/rust-lang/crates.io-index"
1614+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1615+
1616+[[package]]
1617+name = "proc-macro2"
1618+version = "1.0.69"
1619+source = "registry+https://github.com/rust-lang/crates.io-index"
1620+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
1621+dependencies = [
1622+ "unicode-ident",
1623+]
1624+
1625+[[package]]
1626+name = "prodash"
1627+version = "28.0.0"
1628+source = "registry+https://github.com/rust-lang/crates.io-index"
1629+checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
1630+
1631+[[package]]
1632+name = "pulldown-cmark"
1633+version = "0.9.3"
1634+source = "registry+https://github.com/rust-lang/crates.io-index"
1635+checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
1636+dependencies = [
1637+ "bitflags 1.3.2",
1638+ "memchr",
1639+ "unicase",
1640+]
1641+
1642+[[package]]
1643+name = "quickcheck"
1644+version = "1.0.3"
1645+source = "registry+https://github.com/rust-lang/crates.io-index"
1646+checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
1647+dependencies = [
1648+ "rand",
1649+]
1650+
1651+[[package]]
1652+name = "quote"
1653+version = "1.0.29"
1654+source = "registry+https://github.com/rust-lang/crates.io-index"
1655+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
1656+dependencies = [
1657+ "proc-macro2",
1658+]
1659+
1660+[[package]]
1661+name = "rand"
1662+version = "0.8.5"
1663+source = "registry+https://github.com/rust-lang/crates.io-index"
1664+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1665+dependencies = [
1666+ "rand_core",
1667+]
1668+
1669+[[package]]
1670+name = "rand_core"
1671+version = "0.6.4"
1672+source = "registry+https://github.com/rust-lang/crates.io-index"
1673+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1674+dependencies = [
1675+ "getrandom",
1676+]
1677+
1678+[[package]]
1679+name = "rayon"
1680+version = "1.7.0"
1681+source = "registry+https://github.com/rust-lang/crates.io-index"
1682+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
1683+dependencies = [
1684+ "either",
1685+ "rayon-core",
1686+]
1687+
1688+[[package]]
1689+name = "rayon-core"
1690+version = "1.11.0"
1691+source = "registry+https://github.com/rust-lang/crates.io-index"
1692+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
1693+dependencies = [
1694+ "crossbeam-channel",
1695+ "crossbeam-deque",
1696+ "crossbeam-utils",
524 "num_cpus",
1697 "num_cpus",
525− "smol",
526− "tui",
527]
1698]
528
1699
529[[package]]
1700[[package]]
530−name = "helix-view"
1701+name = "redox_syscall"
531−version = "0.1.0"
1702+version = "0.2.16"
1703+source = "registry+https://github.com/rust-lang/crates.io-index"
1704+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
532dependencies = [
1705dependencies = [
533− "anyhow",
1706+ "bitflags 1.3.2",
534− "crossterm",
535− "helix-core",
536− "once_cell",
537− "smol",
538− "tui",
539− "url",
540]
1707]
541
1708
542[[package]]
1709[[package]]
543−name = "hermit-abi"
1710+name = "redox_syscall"
1711+version = "0.4.1"
1712+source = "registry+https://github.com/rust-lang/crates.io-index"
1713+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
1714+dependencies = [
1715+ "bitflags 1.3.2",
1716+]
1717+
1718+[[package]]
1719+name = "regex"
1720+version = "1.10.2"
1721+source = "registry+https://github.com/rust-lang/crates.io-index"
1722+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
1723+dependencies = [
1724+ "aho-corasick",
1725+ "memchr",
1726+ "regex-automata",
1727+ "regex-syntax",
1728+]
1729+
1730+[[package]]
1731+name = "regex-automata"
1732+version = "0.4.3"
1733+source = "registry+https://github.com/rust-lang/crates.io-index"
1734+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1735+dependencies = [
1736+ "aho-corasick",
1737+ "memchr",
1738+ "regex-syntax",
1739+]
1740+
1741+[[package]]
1742+name = "regex-syntax"
1743+version = "0.8.2"
1744+source = "registry+https://github.com/rust-lang/crates.io-index"
1745+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
1746+
1747+[[package]]
1748+name = "ropey"
1749+version = "1.6.1"
1750+source = "registry+https://github.com/rust-lang/crates.io-index"
1751+checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
1752+dependencies = [
1753+ "smallvec",
1754+ "str_indices",
1755+]
1756+
1757+[[package]]
1758+name = "rustc-demangle"
544−version = "0.1.17"
1759+version = "0.1.23"
1760+source = "registry+https://github.com/rust-lang/crates.io-index"
1761+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1762+
1763+[[package]]
1764+name = "rustix"
1765+version = "0.38.28"
545source = "registry+https://github.com/rust-lang/crates.io-index"
1766source = "registry+https://github.com/rust-lang/crates.io-index"
546−checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
1767+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
547dependencies = [
1768dependencies = [
1769+ "bitflags 2.4.1",
1770+ "errno",
548 "libc",
1771 "libc",
1772+ "linux-raw-sys",
1773+ "windows-sys 0.52.0",
549]
1774]
550
1775
551[[package]]
1776[[package]]
552−name = "idna"
1777+name = "ryu"
553−version = "0.2.0"
1778+version = "1.0.13"
554source = "registry+https://github.com/rust-lang/crates.io-index"
1779source = "registry+https://github.com/rust-lang/crates.io-index"
555−checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
1780+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
1781+
1782+[[package]]
1783+name = "same-file"
1784+version = "1.0.6"
1785+source = "registry+https://github.com/rust-lang/crates.io-index"
1786+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
556dependencies = [
1787dependencies = [
557− "matches",
1788+ "winapi-util",
558− "unicode-bidi",
559− "unicode-normalization",
560]
1789]
561
1790
562[[package]]
1791[[package]]
563−name = "indexmap"
1792+name = "scopeguard"
1793+version = "1.1.0"
1794+source = "registry+https://github.com/rust-lang/crates.io-index"
1795+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1796+
1797+[[package]]
1798+name = "scratch"
1799+version = "1.0.5"
1800+source = "registry+https://github.com/rust-lang/crates.io-index"
1801+checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
1802+
1803+[[package]]
1804+name = "serde"
1805+version = "1.0.193"
1806+source = "registry+https://github.com/rust-lang/crates.io-index"
1807+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
1808+dependencies = [
1809+ "serde_derive",
1810+]
1811+
1812+[[package]]
1813+name = "serde_derive"
1814+version = "1.0.193"
1815+source = "registry+https://github.com/rust-lang/crates.io-index"
1816+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
1817+dependencies = [
1818+ "proc-macro2",
1819+ "quote",
1820+ "syn 2.0.38",
1821+]
1822+
1823+[[package]]
1824+name = "serde_json"
1825+version = "1.0.108"
1826+source = "registry+https://github.com/rust-lang/crates.io-index"
1827+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
1828+dependencies = [
1829+ "itoa",
1830+ "ryu",
1831+ "serde",
1832+]
1833+
1834+[[package]]
1835+name = "serde_repr"
564−version = "1.6.0"
1836+version = "0.1.12"
1837+source = "registry+https://github.com/rust-lang/crates.io-index"
1838+checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
1839+dependencies = [
1840+ "proc-macro2",
1841+ "quote",
1842+ "syn 2.0.38",
1843+]
1844+
1845+[[package]]
1846+name = "serde_spanned"
1847+version = "0.6.3"
1848+source = "registry+https://github.com/rust-lang/crates.io-index"
1849+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
1850+dependencies = [
1851+ "serde",
1852+]
1853+
1854+[[package]]
1855+name = "sha1_smol"
1856+version = "1.0.0"
1857+source = "registry+https://github.com/rust-lang/crates.io-index"
1858+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
1859+
1860+[[package]]
1861+name = "signal-hook"
1862+version = "0.3.17"
1863+source = "registry+https://github.com/rust-lang/crates.io-index"
1864+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
1865+dependencies = [
1866+ "libc",
1867+ "signal-hook-registry",
1868+]
1869+
1870+[[package]]
1871+name = "signal-hook-mio"
1872+version = "0.2.3"
1873+source = "registry+https://github.com/rust-lang/crates.io-index"
1874+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
1875+dependencies = [
1876+ "libc",
1877+ "mio",
1878+ "signal-hook",
1879+]
1880+
1881+[[package]]
1882+name = "signal-hook-registry"
1883+version = "1.4.1"
1884+source = "registry+https://github.com/rust-lang/crates.io-index"
1885+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
1886+dependencies = [
1887+ "libc",
1888+]
1889+
1890+[[package]]
1891+name = "signal-hook-tokio"
1892+version = "0.3.1"
1893+source = "registry+https://github.com/rust-lang/crates.io-index"
1894+checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e"
1895+dependencies = [
1896+ "futures-core",
1897+ "libc",
1898+ "signal-hook",
1899+ "tokio",
1900+]
1901+
1902+[[package]]
1903+name = "slab"
1904+version = "0.4.8"
565source = "registry+https://github.com/rust-lang/crates.io-index"
1905source = "registry+https://github.com/rust-lang/crates.io-index"
566−checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
1906+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
567dependencies = [
1907dependencies = [
568 "autocfg",
1908 "autocfg",
569− "hashbrown",
570]
1909]
571
1910
572[[package]]
1911[[package]]
573−name = "instant"
1912+name = "slotmap"
574−version = "0.1.9"
1913+version = "1.0.7"
575source = "registry+https://github.com/rust-lang/crates.io-index"
1914source = "registry+https://github.com/rust-lang/crates.io-index"
576−checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
1915+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
577dependencies = [
1916dependencies = [
578− "cfg-if 1.0.0",
1917+ "version_check",
579]
1918]
580
1919
581[[package]]
1920[[package]]
582−name = "itoa"
1921+name = "smallvec"
1922+version = "1.11.2"
1923+source = "registry+https://github.com/rust-lang/crates.io-index"
1924+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
1925+
1926+[[package]]
1927+name = "smartstring"
1928+version = "1.0.1"
1929+source = "registry+https://github.com/rust-lang/crates.io-index"
1930+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
1931+dependencies = [
1932+ "autocfg",
1933+ "static_assertions",
1934+ "version_check",
1935+]
1936+
1937+[[package]]
1938+name = "smawk"
583−version = "0.4.6"
1939+version = "0.3.1"
584source = "registry+https://github.com/rust-lang/crates.io-index"
1940source = "registry+https://github.com/rust-lang/crates.io-index"
585−checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
1941+checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
586
1942
587[[package]]
1943[[package]]
588−name = "jobserver"
1944+name = "socket2"
589−version = "0.1.21"
1945+version = "0.5.5"
590source = "registry+https://github.com/rust-lang/crates.io-index"
1946source = "registry+https://github.com/rust-lang/crates.io-index"
591−checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
1947+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
592dependencies = [
1948dependencies = [
593 "libc",
1949 "libc",
1950+ "windows-sys 0.48.0",
594]
1951]
595
1952
596[[package]]
1953[[package]]
597−name = "jsonrpc-core"
1954+name = "static_assertions"
1955+version = "1.1.0"
1956+source = "registry+https://github.com/rust-lang/crates.io-index"
1957+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1958+
1959+[[package]]
1960+name = "str_indices"
598−version = "15.1.0"
1961+version = "0.4.1"
1962+source = "registry+https://github.com/rust-lang/crates.io-index"
1963+checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd"
1964+
1965+[[package]]
1966+name = "syn"
1967+version = "1.0.109"
599source = "registry+https://github.com/rust-lang/crates.io-index"
1968source = "registry+https://github.com/rust-lang/crates.io-index"
600−checksum = "0745a6379e3edc893c84ec203589790774e4247420033e71a76d3ab4687991fa"
1969+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
601dependencies = [
1970dependencies = [
602− "futures",
1971+ "proc-macro2",
603− "log",
1972+ "quote",
604− "serde",
1973+ "unicode-ident",
605− "serde_derive",
606− "serde_json",
607]
1974]
608
1975
609[[package]]
1976[[package]]
610−name = "lazy_static"
1977+name = "syn"
611−version = "1.4.0"
1978+version = "2.0.38"
612source = "registry+https://github.com/rust-lang/crates.io-index"
1979source = "registry+https://github.com/rust-lang/crates.io-index"
613−checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1980+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
1981+dependencies = [
1982+ "proc-macro2",
1983+ "quote",
1984+ "unicode-ident",
1985+]
614
1986
615[[package]]
1987[[package]]
616−name = "libc"
1988+name = "tempfile"
617−version = "0.2.80"
1989+version = "3.8.1"
618source = "registry+https://github.com/rust-lang/crates.io-index"
1990source = "registry+https://github.com/rust-lang/crates.io-index"
619−checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
1991+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
1992+dependencies = [
1993+ "cfg-if",
1994+ "fastrand",
1995+ "redox_syscall 0.4.1",
1996+ "rustix",
1997+ "windows-sys 0.48.0",
1998+]
620
1999
621[[package]]
2000[[package]]
622−name = "lock_api"
2001+name = "termcolor"
623−version = "0.4.2"
2002+version = "1.2.0"
624source = "registry+https://github.com/rust-lang/crates.io-index"
2003source = "registry+https://github.com/rust-lang/crates.io-index"
625−checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
2004+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
626dependencies = [
2005dependencies = [
627− "scopeguard",
2006+ "winapi-util",
628]
2007]
629
2008
630[[package]]
2009[[package]]
631−name = "log"
2010+name = "termini"
632−version = "0.4.11"
2011+version = "1.0.0"
633source = "registry+https://github.com/rust-lang/crates.io-index"
2012source = "registry+https://github.com/rust-lang/crates.io-index"
634−checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
2013+checksum = "2ad441d87dd98bc5eeb31cf2fb7e4839968763006b478efb38668a3bf9da0d59"
635dependencies = [
2014dependencies = [
636− "cfg-if 0.1.10",
2015+ "home",
637]
2016]
638
2017
639[[package]]
2018[[package]]
640−name = "lsp-types"
2019+name = "textwrap"
641−version = "0.84.0"
2020+version = "0.16.0"
642source = "registry+https://github.com/rust-lang/crates.io-index"
2021source = "registry+https://github.com/rust-lang/crates.io-index"
643−checksum = "3b95be71fe205e44de754185bcf86447b65813ce1ceb298f8d3793ade5fff08d"
2022+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
644dependencies = [
2023dependencies = [
645− "base64 0.12.3",
2024+ "smawk",
646− "bitflags",
2025+ "unicode-linebreak",
647− "serde",
2026+ "unicode-width",
648− "serde_json",
649− "serde_repr",
650− "url",
651]
2027]
652
2028
653[[package]]
2029[[package]]
654−name = "mac"
2030+name = "thiserror"
655−version = "0.1.1"
2031+version = "1.0.52"
656source = "registry+https://github.com/rust-lang/crates.io-index"
2032source = "registry+https://github.com/rust-lang/crates.io-index"
657−checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
2033+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
2034+dependencies = [
2035+ "thiserror-impl",
2036+]
658
2037
659[[package]]
2038[[package]]
660−name = "matches"
2039+name = "thiserror-impl"
661−version = "0.1.8"
2040+version = "1.0.52"
662source = "registry+https://github.com/rust-lang/crates.io-index"
2041source = "registry+https://github.com/rust-lang/crates.io-index"
663−checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
2042+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
2043+dependencies = [
2044+ "proc-macro2",
2045+ "quote",
2046+ "syn 2.0.38",
2047+]
664
2048
665[[package]]
2049[[package]]
666−name = "memchr"
2050+name = "threadpool"
667−version = "2.3.4"
2051+version = "1.8.1"
668source = "registry+https://github.com/rust-lang/crates.io-index"
2052source = "registry+https://github.com/rust-lang/crates.io-index"
669−checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
2053+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
2054+dependencies = [
2055+ "num_cpus",
2056+]
670
2057
671[[package]]
2058[[package]]
672−name = "mio"
2059+name = "time"
673−version = "0.7.6"
2060+version = "0.3.23"
674source = "registry+https://github.com/rust-lang/crates.io-index"
2061source = "registry+https://github.com/rust-lang/crates.io-index"
675−checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b"
2062+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
676dependencies = [
2063dependencies = [
2064+ "itoa",
677 "libc",
2065 "libc",
678− "log",
2066+ "num_threads",
679− "miow",
2067+ "serde",
680− "ntapi",
2068+ "time-core",
681− "winapi",
2069+ "time-macros",
2070+]
2071+
2072+[[package]]
2073+name = "time-core"
2074+version = "0.1.1"
2075+source = "registry+https://github.com/rust-lang/crates.io-index"
2076+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
2077+
2078+[[package]]
2079+name = "time-macros"
2080+version = "0.2.10"
2081+source = "registry+https://github.com/rust-lang/crates.io-index"
2082+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
2083+dependencies = [
2084+ "time-core",
682]
2085]
683
2086
684[[package]]
2087[[package]]
685−name = "miow"
2088+name = "tinyvec"
686−version = "0.3.6"
2089+version = "1.6.0"
687source = "registry+https://github.com/rust-lang/crates.io-index"
2090source = "registry+https://github.com/rust-lang/crates.io-index"
688−checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
2091+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
689dependencies = [
2092dependencies = [
690− "socket2",
2093+ "tinyvec_macros",
691− "winapi",
692]
2094]
693
2095
694[[package]]
2096[[package]]
695−name = "nb-connect"
2097+name = "tinyvec_macros"
696−version = "1.0.2"
2098+version = "0.1.1"
697source = "registry+https://github.com/rust-lang/crates.io-index"
2099source = "registry+https://github.com/rust-lang/crates.io-index"
698−checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998"
2100+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2101+
2102+[[package]]
2103+name = "tokio"
2104+version = "1.35.1"
2105+source = "registry+https://github.com/rust-lang/crates.io-index"
2106+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
699dependencies = [
2107dependencies = [
2108+ "backtrace",
2109+ "bytes",
700 "libc",
2110 "libc",
701− "winapi",
2111+ "mio",
2112+ "num_cpus",
2113+ "parking_lot",
2114+ "pin-project-lite",
2115+ "signal-hook-registry",
2116+ "socket2",
2117+ "tokio-macros",
2118+ "windows-sys 0.48.0",
702]
2119]
703
2120
704[[package]]
2121[[package]]
705−name = "new_debug_unreachable"
2122+name = "tokio-macros"
706−version = "1.0.4"
2123+version = "2.2.0"
707source = "registry+https://github.com/rust-lang/crates.io-index"
2124source = "registry+https://github.com/rust-lang/crates.io-index"
708−checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
2125+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
2126+dependencies = [
2127+ "proc-macro2",
2128+ "quote",
2129+ "syn 2.0.38",
2130+]
709
2131
710[[package]]
2132[[package]]
711−name = "ntapi"
2133+name = "tokio-stream"
712−version = "0.3.6"
2134+version = "0.1.14"
713source = "registry+https://github.com/rust-lang/crates.io-index"
2135source = "registry+https://github.com/rust-lang/crates.io-index"
714−checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
2136+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
715dependencies = [
2137dependencies = [
716− "winapi",
2138+ "futures-core",
2139+ "pin-project-lite",
2140+ "tokio",
717]
2141]
718
2142
719[[package]]
2143[[package]]
720−name = "num-integer"
2144+name = "toml"
721−version = "0.1.44"
2145+version = "0.7.6"
722source = "registry+https://github.com/rust-lang/crates.io-index"
2146source = "registry+https://github.com/rust-lang/crates.io-index"
723−checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
2147+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
724dependencies = [
2148dependencies = [
725− "autocfg",
2149+ "serde",
726− "num-traits",
2150+ "serde_spanned",
2151+ "toml_datetime",
2152+ "toml_edit",
727]
2153]
728
2154
729[[package]]
2155[[package]]
730−name = "num-traits"
2156+name = "toml_datetime"
731−version = "0.2.14"
2157+version = "0.6.3"
732source = "registry+https://github.com/rust-lang/crates.io-index"
2158source = "registry+https://github.com/rust-lang/crates.io-index"
733−checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
2159+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
734dependencies = [
2160dependencies = [
735− "autocfg",
2161+ "serde",
736]
2162]
737
2163
738[[package]]
2164[[package]]
739−name = "num_cpus"
2165+name = "toml_edit"
740−version = "1.13.0"
2166+version = "0.19.12"
741source = "registry+https://github.com/rust-lang/crates.io-index"
2167source = "registry+https://github.com/rust-lang/crates.io-index"
742−checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
2168+checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
743dependencies = [
2169dependencies = [
744− "hermit-abi",
2170+ "indexmap",
745− "libc",
2171+ "serde",
2172+ "serde_spanned",
2173+ "toml_datetime",
2174+ "winnow 0.4.6",
746]
2175]
747
2176
748[[package]]
2177[[package]]
749−name = "once_cell"
2178+name = "tree-sitter"
750−version = "1.5.2"
2179+version = "0.20.10"
751−source = "registry+https://github.com/rust-lang/crates.io-index"
2180+source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
752−checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
2181+dependencies = [
2182+ "cc",
2183+ "regex",
2184+]
753
2185
754[[package]]
2186[[package]]
755−name = "os_str_bytes"
2187+name = "unicase"
756−version = "2.4.0"
2188+version = "2.6.0"
757source = "registry+https://github.com/rust-lang/crates.io-index"
2189source = "registry+https://github.com/rust-lang/crates.io-index"
758−checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
2190+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
2191+dependencies = [
2192+ "version_check",
2193+]
759
2194
760[[package]]
2195[[package]]
761−name = "parking"
2196+name = "unicode-bidi"
762−version = "2.0.0"
2197+version = "0.3.13"
763source = "registry+https://github.com/rust-lang/crates.io-index"
2198source = "registry+https://github.com/rust-lang/crates.io-index"
764−checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
2199+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
765
2200
766[[package]]
2201[[package]]
767−name = "parking_lot"
2202+name = "unicode-bom"
768−version = "0.11.1"
2203+version = "2.0.2"
769source = "registry+https://github.com/rust-lang/crates.io-index"
2204source = "registry+https://github.com/rust-lang/crates.io-index"
770−checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
2205+checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552"
771−dependencies = [
772− "instant",
773− "lock_api",
774− "parking_lot_core",
775−]
776
2206
777[[package]]
2207[[package]]
778−name = "parking_lot_core"
2208+name = "unicode-general-category"
779−version = "0.8.0"
2209+version = "0.6.0"
780source = "registry+https://github.com/rust-lang/crates.io-index"
2210source = "registry+https://github.com/rust-lang/crates.io-index"
781−checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
2211+checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7"
782−dependencies = [
783− "cfg-if 0.1.10",
784− "cloudabi",
785− "instant",
786− "libc",
787− "redox_syscall",
788− "smallvec",
789− "winapi",
790−]
791
2212
792[[package]]
2213[[package]]
793−name = "pathdiff"
2214+name = "unicode-ident"
794−version = "0.2.0"
2215+version = "1.0.8"
795source = "registry+https://github.com/rust-lang/crates.io-index"
2216source = "registry+https://github.com/rust-lang/crates.io-index"
796−checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34"
2217+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
797
2218
798[[package]]
2219[[package]]
799−name = "percent-encoding"
2220+name = "unicode-linebreak"
800−version = "2.1.0"
2221+version = "0.1.5"
801source = "registry+https://github.com/rust-lang/crates.io-index"
2222source = "registry+https://github.com/rust-lang/crates.io-index"
802−checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
2223+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
803
2224
804[[package]]
2225[[package]]
805−name = "pin-project"
2226+name = "unicode-normalization"
806−version = "1.0.2"
2227+version = "0.1.22"
807source = "registry+https://github.com/rust-lang/crates.io-index"
2228source = "registry+https://github.com/rust-lang/crates.io-index"
808−checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
2229+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
809dependencies = [
2230dependencies = [
810− "pin-project-internal",
2231+ "tinyvec",
811]
2232]
812
2233
813[[package]]
2234[[package]]
814−name = "pin-project-internal"
2235+name = "unicode-segmentation"
815−version = "1.0.2"
2236+version = "1.10.1"
816source = "registry+https://github.com/rust-lang/crates.io-index"
2237source = "registry+https://github.com/rust-lang/crates.io-index"
817−checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
2238+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
818−dependencies = [
819− "proc-macro2",
820− "quote",
821− "syn",
822−]
823
2239
824[[package]]
2240[[package]]
825−name = "pin-project-lite"
2241+name = "unicode-width"
826version = "0.1.11"
2242version = "0.1.11"
827source = "registry+https://github.com/rust-lang/crates.io-index"
2243source = "registry+https://github.com/rust-lang/crates.io-index"
828−checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
2244+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
829
2245
830[[package]]
2246[[package]]
831−name = "pin-utils"
2247+name = "url"
832−version = "0.1.0"
2248+version = "2.5.0"
833source = "registry+https://github.com/rust-lang/crates.io-index"
2249source = "registry+https://github.com/rust-lang/crates.io-index"
834−checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
2250+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
2251+dependencies = [
2252+ "form_urlencoded",
2253+ "idna",
2254+ "percent-encoding",
2255+ "serde",
2256+]
835
2257
836[[package]]
2258[[package]]
837−name = "polling"
2259+name = "version_check"
838−version = "2.0.2"
2260+version = "0.9.4"
839source = "registry+https://github.com/rust-lang/crates.io-index"
2261source = "registry+https://github.com/rust-lang/crates.io-index"
840−checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4"
2262+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
841−dependencies = [
842− "cfg-if 0.1.10",
843− "libc",
844− "log",
845− "wepoll-sys",
846− "winapi",
847−]
848
2263
849[[package]]
2264[[package]]
850−name = "proc-macro-hack"
2265+name = "walkdir"
851−version = "0.5.19"
2266+version = "2.4.0"
852source = "registry+https://github.com/rust-lang/crates.io-index"
2267source = "registry+https://github.com/rust-lang/crates.io-index"
853−checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
2268+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
2269+dependencies = [
2270+ "same-file",
2271+ "winapi-util",
2272+]
854
2273
855[[package]]
2274[[package]]
856−name = "proc-macro-nested"
2275+name = "wasi"
857−version = "0.1.6"
2276+version = "0.11.0+wasi-snapshot-preview1"
858source = "registry+https://github.com/rust-lang/crates.io-index"
2277source = "registry+https://github.com/rust-lang/crates.io-index"
859−checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
2278+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
860
2279
861[[package]]
2280[[package]]
862−name = "proc-macro2"
2281+name = "wasm-bindgen"
863−version = "1.0.24"
2282+version = "0.2.84"
864source = "registry+https://github.com/rust-lang/crates.io-index"
2283source = "registry+https://github.com/rust-lang/crates.io-index"
865−checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
2284+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
866dependencies = [
2285dependencies = [
867− "unicode-xid",
2286+ "cfg-if",
2287+ "wasm-bindgen-macro",
868]
2288]
869
2289
870[[package]]
2290[[package]]
871−name = "quote"
2291+name = "wasm-bindgen-backend"
872−version = "1.0.7"
2292+version = "0.2.84"
873source = "registry+https://github.com/rust-lang/crates.io-index"
2293source = "registry+https://github.com/rust-lang/crates.io-index"
874−checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
2294+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
875dependencies = [
2295dependencies = [
2296+ "bumpalo",
2297+ "log",
2298+ "once_cell",
876 "proc-macro2",
2299 "proc-macro2",
2300+ "quote",
2301+ "syn 1.0.109",
2302+ "wasm-bindgen-shared",
877]
2303]
878
2304
879[[package]]
2305[[package]]
880−name = "redox_syscall"
2306+name = "wasm-bindgen-macro"
881−version = "0.1.57"
882−source = "registry+https://github.com/rust-lang/crates.io-index"
883−checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
884−
885−[[package]]
886−name = "redox_users"
887−version = "0.3.5"
2307+version = "0.2.84"
888source = "registry+https://github.com/rust-lang/crates.io-index"
2308source = "registry+https://github.com/rust-lang/crates.io-index"
889−checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
2309+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
890dependencies = [
2310dependencies = [
891− "getrandom",
2311+ "quote",
892− "redox_syscall",
2312+ "wasm-bindgen-macro-support",
893− "rust-argon2",
894]
2313]
895
2314
896[[package]]
2315[[package]]
897−name = "regex"
2316+name = "wasm-bindgen-macro-support"
898−version = "1.4.2"
2317+version = "0.2.84"
899source = "registry+https://github.com/rust-lang/crates.io-index"
2318source = "registry+https://github.com/rust-lang/crates.io-index"
900−checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
2319+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
901dependencies = [
2320dependencies = [
902− "aho-corasick",
2321+ "proc-macro2",
903− "memchr",
2322+ "quote",
904− "regex-syntax",
2323+ "syn 1.0.109",
905− "thread_local",
2324+ "wasm-bindgen-backend",
2325+ "wasm-bindgen-shared",
906]
2326]
907
2327
908[[package]]
2328[[package]]
909−name = "regex-syntax"
2329+name = "wasm-bindgen-shared"
910−version = "0.6.21"
2330+version = "0.2.84"
911source = "registry+https://github.com/rust-lang/crates.io-index"
2331source = "registry+https://github.com/rust-lang/crates.io-index"
912−checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
2332+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
913
2333
914[[package]]
2334[[package]]
915−name = "ropey"
2335+name = "which"
916−version = "1.2.0"
2336+version = "5.0.0"
917source = "registry+https://github.com/rust-lang/crates.io-index"
2337source = "registry+https://github.com/rust-lang/crates.io-index"
918−checksum = "f0f3ef16589fdbb3e8fbce3dca944c08e61f39c7f16064b21a257d68ea911a83"
2338+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
919dependencies = [
2339dependencies = [
920− "smallvec",
2340+ "either",
2341+ "home",
2342+ "once_cell",
2343+ "rustix",
2344+ "windows-sys 0.48.0",
921]
2345]
922
2346
923[[package]]
2347[[package]]
924−name = "rust-argon2"
2348+name = "winapi"
925−version = "0.8.3"
2349+version = "0.3.9"
926source = "registry+https://github.com/rust-lang/crates.io-index"
2350source = "registry+https://github.com/rust-lang/crates.io-index"
927−checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
2351+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
928dependencies = [
2352dependencies = [
929− "base64 0.13.0",
2353+ "winapi-i686-pc-windows-gnu",
930− "blake2b_simd",
2354+ "winapi-x86_64-pc-windows-gnu",
931− "constant_time_eq",
932− "crossbeam-utils",
933]
2355]
934
2356
935[[package]]
2357[[package]]
936−name = "ryu"
2358+name = "winapi-i686-pc-windows-gnu"
937−version = "1.0.5"
938−source = "registry+https://github.com/rust-lang/crates.io-index"
939−checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
940−
941−[[package]]
942−name = "scopeguard"
943−version = "1.1.0"
2359+version = "0.4.0"
944source = "registry+https://github.com/rust-lang/crates.io-index"
2360source = "registry+https://github.com/rust-lang/crates.io-index"
945−checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
2361+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
946
2362
947[[package]]
2363[[package]]
948−name = "serde"
2364+name = "winapi-util"
949−version = "1.0.117"
2365+version = "0.1.5"
950source = "registry+https://github.com/rust-lang/crates.io-index"
2366source = "registry+https://github.com/rust-lang/crates.io-index"
951−checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
2367+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
952dependencies = [
2368dependencies = [
953− "serde_derive",
2369+ "winapi",
954]
2370]
955
2371
956[[package]]
2372[[package]]
957−name = "serde_derive"
2373+name = "winapi-x86_64-pc-windows-gnu"
958−version = "1.0.117"
2374+version = "0.4.0"
959source = "registry+https://github.com/rust-lang/crates.io-index"
2375source = "registry+https://github.com/rust-lang/crates.io-index"
960−checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
2376+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
961−dependencies = [
962− "proc-macro2",
963− "quote",
964− "syn",
965−]
966
2377
967[[package]]
2378[[package]]
968−name = "serde_json"
2379+name = "windows"
969−version = "1.0.59"
2380+version = "0.48.0"
970source = "registry+https://github.com/rust-lang/crates.io-index"
2381source = "registry+https://github.com/rust-lang/crates.io-index"
971−checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
2382+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
972dependencies = [
2383dependencies = [
973− "itoa",
2384+ "windows-targets 0.48.0",
974− "ryu",
975− "serde",
976]
2385]
977
2386
978[[package]]
2387[[package]]
979−name = "serde_repr"
2388+name = "windows"
980−version = "0.1.6"
2389+version = "0.52.0"
981source = "registry+https://github.com/rust-lang/crates.io-index"
2390source = "registry+https://github.com/rust-lang/crates.io-index"
982−checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76"
2391+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
983dependencies = [
2392dependencies = [
984− "proc-macro2",
2393+ "windows-core",
985− "quote",
2394+ "windows-targets 0.52.0",
986− "syn",
987]
2395]
988
2396
989[[package]]
2397[[package]]
990−name = "shellexpand"
2398+name = "windows-core"
991−version = "2.0.0"
2399+version = "0.52.0"
992source = "registry+https://github.com/rust-lang/crates.io-index"
2400source = "registry+https://github.com/rust-lang/crates.io-index"
993−checksum = "9a2b22262a9aaf9464d356f656fea420634f78c881c5eebd5ef5e66d8b9bc603"
2401+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
994dependencies = [
2402dependencies = [
995− "dirs",
2403+ "windows-targets 0.52.0",
996]
2404]
997
2405
998[[package]]
2406[[package]]
999−name = "signal-hook"
2407+name = "windows-sys"
1000−version = "0.1.16"
2408+version = "0.45.0"
1001source = "registry+https://github.com/rust-lang/crates.io-index"
2409source = "registry+https://github.com/rust-lang/crates.io-index"
1002−checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
2410+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
1003dependencies = [
2411dependencies = [
1004− "libc",
2412+ "windows-targets 0.42.2",
1005− "mio",
1006− "signal-hook-registry",
1007]
2413]
1008
2414
1009[[package]]
2415[[package]]
1010−name = "signal-hook-registry"
2416+name = "windows-sys"
1011−version = "1.2.2"
2417+version = "0.48.0"
1012source = "registry+https://github.com/rust-lang/crates.io-index"
2418source = "registry+https://github.com/rust-lang/crates.io-index"
1013−checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
2419+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
1014dependencies = [
2420dependencies = [
1015− "libc",
2421+ "windows-targets 0.48.0",
1016]
2422]
1017
2423
1018[[package]]
2424[[package]]
1019−name = "slab"
2425+name = "windows-sys"
1020−version = "0.4.2"
2426+version = "0.52.0"
1021−source = "registry+https://github.com/rust-lang/crates.io-index"
1022−checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1023−
1024−[[package]]
1025−name = "smallvec"
1026−version = "1.5.0"
1027−source = "registry+https://github.com/rust-lang/crates.io-index"
1028−checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
1029−
1030−[[package]]
1031−name = "smol"
1032−version = "1.2.5"
1033source = "registry+https://github.com/rust-lang/crates.io-index"
2427source = "registry+https://github.com/rust-lang/crates.io-index"
1034−checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
2428+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1035dependencies = [
2429dependencies = [
1036− "async-channel",
2430+ "windows-targets 0.52.0",
1037− "async-executor",
1038− "async-fs",
1039− "async-io",
1040− "async-lock",
1041− "async-net",
1042− "async-process",
1043− "blocking",
1044− "futures-lite",
1045− "once_cell",
1046]
2431]
1047
2432
1048[[package]]
2433[[package]]
1049−name = "socket2"
2434+name = "windows-targets"
1050−version = "0.3.17"
2435+version = "0.42.2"
1051source = "registry+https://github.com/rust-lang/crates.io-index"
2436source = "registry+https://github.com/rust-lang/crates.io-index"
1052−checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
2437+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
1053dependencies = [
2438dependencies = [
1054− "cfg-if 1.0.0",
2439+ "windows_aarch64_gnullvm 0.42.2",
1055− "libc",
2440+ "windows_aarch64_msvc 0.42.2",
1056− "redox_syscall",
2441+ "windows_i686_gnu 0.42.2",
1057− "winapi",
2442+ "windows_i686_msvc 0.42.2",
2443+ "windows_x86_64_gnu 0.42.2",
2444+ "windows_x86_64_gnullvm 0.42.2",
2445+ "windows_x86_64_msvc 0.42.2",
1058]
2446]
1059
2447
1060[[package]]
2448[[package]]
1061−name = "syn"
2449+name = "windows-targets"
1062−version = "1.0.53"
2450+version = "0.48.0"
1063source = "registry+https://github.com/rust-lang/crates.io-index"
2451source = "registry+https://github.com/rust-lang/crates.io-index"
1064−checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
2452+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
1065dependencies = [
2453dependencies = [
1066− "proc-macro2",
2454+ "windows_aarch64_gnullvm 0.48.0",
1067− "quote",
2455+ "windows_aarch64_msvc 0.48.0",
1068− "unicode-xid",
2456+ "windows_i686_gnu 0.48.0",
2457+ "windows_i686_msvc 0.48.0",
2458+ "windows_x86_64_gnu 0.48.0",
2459+ "windows_x86_64_gnullvm 0.48.0",
2460+ "windows_x86_64_msvc 0.48.0",
1069]
2461]
1070
2462
1071[[package]]
2463[[package]]
1072−name = "tendril"
2464+name = "windows-targets"
1073−version = "0.4.1"
2465+version = "0.52.0"
1074−source = "git+https://github.com/servo/tendril#9532724c32a0bf5e65acb56209373d97223bc530"
2466+source = "registry+https://github.com/rust-lang/crates.io-index"
2467+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
1075dependencies = [
2468dependencies = [
1076− "futf",
2469+ "windows_aarch64_gnullvm 0.52.0",
1077− "mac",
2470+ "windows_aarch64_msvc 0.52.0",
1078− "utf-8",
2471+ "windows_i686_gnu 0.52.0",
2472+ "windows_i686_msvc 0.52.0",
2473+ "windows_x86_64_gnu 0.52.0",
2474+ "windows_x86_64_gnullvm 0.52.0",
2475+ "windows_x86_64_msvc 0.52.0",
1079]
2476]
1080
2477
1081[[package]]
2478[[package]]
1082−name = "textwrap"
2479+name = "windows_aarch64_gnullvm"
1083−version = "0.12.1"
2480+version = "0.42.2"
1084source = "registry+https://github.com/rust-lang/crates.io-index"
2481source = "registry+https://github.com/rust-lang/crates.io-index"
1085−checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
2482+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
1086−dependencies = [
1087− "unicode-width",
1088−]
1089
2483
1090[[package]]
2484[[package]]
1091−name = "thiserror"
2485+name = "windows_aarch64_gnullvm"
1092−version = "1.0.22"
2486+version = "0.48.0"
1093source = "registry+https://github.com/rust-lang/crates.io-index"
2487source = "registry+https://github.com/rust-lang/crates.io-index"
1094−checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
2488+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
1095−dependencies = [
1096− "thiserror-impl",
1097−]
1098
2489
1099[[package]]
2490[[package]]
1100−name = "thiserror-impl"
2491+name = "windows_aarch64_gnullvm"
1101−version = "1.0.22"
2492+version = "0.52.0"
1102source = "registry+https://github.com/rust-lang/crates.io-index"
2493source = "registry+https://github.com/rust-lang/crates.io-index"
1103−checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
2494+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
1104−dependencies = [
1105− "proc-macro2",
1106− "quote",
1107− "syn",
1108−]
1109
2495
1110[[package]]
2496[[package]]
1111−name = "thread_local"
2497+name = "windows_aarch64_msvc"
1112−version = "1.0.1"
2498+version = "0.42.2"
1113source = "registry+https://github.com/rust-lang/crates.io-index"
2499source = "registry+https://github.com/rust-lang/crates.io-index"
1114−checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
2500+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
1115−dependencies = [
1116− "lazy_static",
1117−]
1118
2501
1119[[package]]
2502[[package]]
1120−name = "time"
2503+name = "windows_aarch64_msvc"
1121−version = "0.1.44"
2504+version = "0.48.0"
1122source = "registry+https://github.com/rust-lang/crates.io-index"
2505source = "registry+https://github.com/rust-lang/crates.io-index"
1123−checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
2506+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
1124−dependencies = [
1125− "libc",
1126− "wasi 0.10.0+wasi-snapshot-preview1",
1127− "winapi",
1128−]
1129
2507
1130[[package]]
2508[[package]]
1131−name = "tinyvec"
2509+name = "windows_aarch64_msvc"
1132−version = "1.1.0"
2510+version = "0.52.0"
1133source = "registry+https://github.com/rust-lang/crates.io-index"
2511source = "registry+https://github.com/rust-lang/crates.io-index"
1134−checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
2512+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
1135−dependencies = [
1136− "tinyvec_macros",
1137−]
1138
2513
1139[[package]]
2514[[package]]
1140−name = "tinyvec_macros"
2515+name = "windows_i686_gnu"
1141−version = "0.1.0"
2516+version = "0.42.2"
1142source = "registry+https://github.com/rust-lang/crates.io-index"
2517source = "registry+https://github.com/rust-lang/crates.io-index"
1143−checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
2518+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
1144
2519
1145[[package]]
2520[[package]]
1146−name = "tree-sitter"
2521+name = "windows_i686_gnu"
1147−version = "0.17.1"
2522+version = "0.48.0"
1148source = "registry+https://github.com/rust-lang/crates.io-index"
2523source = "registry+https://github.com/rust-lang/crates.io-index"
1149−checksum = "d18dcb776d3affaba6db04d11d645946d34a69b3172e588af96ce9fecd20faac"
2524+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
1150−dependencies = [
1151− "cc",
1152− "regex",
1153−]
1154
2525
1155[[package]]
2526[[package]]
1156−name = "tui"
2527+name = "windows_i686_gnu"
1157−version = "0.13.0"
2528+version = "0.52.0"
1158−source = "git+https://github.com/fdehau/tui-rs#efdd6bfb193dafcb5e3bdc75e7d2d314065da1d7"
2529+source = "registry+https://github.com/rust-lang/crates.io-index"
1159−dependencies = [
2530+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
1160− "bitflags",
1161− "cassowary",
1162− "crossterm",
1163− "unicode-segmentation",
1164− "unicode-width",
1165−]
1166
2531
1167[[package]]
2532[[package]]
1168−name = "unicode-bidi"
2533+name = "windows_i686_msvc"
1169−version = "0.3.4"
2534+version = "0.42.2"
1170source = "registry+https://github.com/rust-lang/crates.io-index"
2535source = "registry+https://github.com/rust-lang/crates.io-index"
1171−checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
2536+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
1172−dependencies = [
1173− "matches",
1174−]
1175
2537
1176[[package]]
2538[[package]]
1177−name = "unicode-normalization"
2539+name = "windows_i686_msvc"
1178−version = "0.1.16"
2540+version = "0.48.0"
1179source = "registry+https://github.com/rust-lang/crates.io-index"
2541source = "registry+https://github.com/rust-lang/crates.io-index"
1180−checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
2542+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
1181−dependencies = [
1182− "tinyvec",
1183−]
1184
2543
1185[[package]]
2544[[package]]
1186−name = "unicode-segmentation"
2545+name = "windows_i686_msvc"
1187−version = "1.7.1"
2546+version = "0.52.0"
1188source = "registry+https://github.com/rust-lang/crates.io-index"
2547source = "registry+https://github.com/rust-lang/crates.io-index"
1189−checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
2548+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
1190
2549
1191[[package]]
2550[[package]]
1192−name = "unicode-width"
2551+name = "windows_x86_64_gnu"
1193−version = "0.1.8"
2552+version = "0.42.2"
1194source = "registry+https://github.com/rust-lang/crates.io-index"
2553source = "registry+https://github.com/rust-lang/crates.io-index"
1195−checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
2554+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
1196
2555
1197[[package]]
2556[[package]]
1198−name = "unicode-xid"
2557+name = "windows_x86_64_gnu"
1199−version = "0.2.1"
2558+version = "0.48.0"
1200source = "registry+https://github.com/rust-lang/crates.io-index"
2559source = "registry+https://github.com/rust-lang/crates.io-index"
1201−checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
2560+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
1202
2561
1203[[package]]
2562[[package]]
1204−name = "url"
2563+name = "windows_x86_64_gnu"
1205−version = "2.2.0"
2564+version = "0.52.0"
1206source = "registry+https://github.com/rust-lang/crates.io-index"
2565source = "registry+https://github.com/rust-lang/crates.io-index"
1207−checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
2566+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
1208−dependencies = [
1209− "form_urlencoded",
1210− "idna",
1211− "matches",
1212− "percent-encoding",
1213− "serde",
1214−]
1215
2567
1216[[package]]
2568[[package]]
1217−name = "utf-8"
2569+name = "windows_x86_64_gnullvm"
1218−version = "0.7.5"
2570+version = "0.42.2"
1219source = "registry+https://github.com/rust-lang/crates.io-index"
2571source = "registry+https://github.com/rust-lang/crates.io-index"
1220−checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
2572+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
1221
2573
1222[[package]]
2574[[package]]
1223−name = "vec-arena"
2575+name = "windows_x86_64_gnullvm"
1224−version = "1.0.0"
2576+version = "0.48.0"
1225source = "registry+https://github.com/rust-lang/crates.io-index"
2577source = "registry+https://github.com/rust-lang/crates.io-index"
1226−checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d"
2578+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
1227
2579
1228[[package]]
2580[[package]]
1229−name = "vec_map"
2581+name = "windows_x86_64_gnullvm"
1230−version = "0.8.2"
2582+version = "0.52.0"
1231source = "registry+https://github.com/rust-lang/crates.io-index"
2583source = "registry+https://github.com/rust-lang/crates.io-index"
1232−checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
2584+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
1233
2585
1234[[package]]
2586[[package]]
1235−name = "waker-fn"
2587+name = "windows_x86_64_msvc"
1236−version = "1.1.0"
2588+version = "0.42.2"
1237source = "registry+https://github.com/rust-lang/crates.io-index"
2589source = "registry+https://github.com/rust-lang/crates.io-index"
1238−checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
2590+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
1239
2591
1240[[package]]
2592[[package]]
1241−name = "wasi"
2593+name = "windows_x86_64_msvc"
1242−version = "0.9.0+wasi-snapshot-preview1"
2594+version = "0.48.0"
1243source = "registry+https://github.com/rust-lang/crates.io-index"
2595source = "registry+https://github.com/rust-lang/crates.io-index"
1244−checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
2596+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
1245
2597
1246[[package]]
2598[[package]]
1247−name = "wasi"
2599+name = "windows_x86_64_msvc"
1248−version = "0.10.0+wasi-snapshot-preview1"
2600+version = "0.52.0"
1249source = "registry+https://github.com/rust-lang/crates.io-index"
2601source = "registry+https://github.com/rust-lang/crates.io-index"
1250−checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
2602+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
1251
2603
1252[[package]]
2604[[package]]
1253−name = "wepoll-sys"
2605+name = "winnow"
1254−version = "3.0.1"
2606+version = "0.4.6"
1255source = "registry+https://github.com/rust-lang/crates.io-index"
2607source = "registry+https://github.com/rust-lang/crates.io-index"
1256−checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff"
2608+checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
1257dependencies = [
2609dependencies = [
1258− "cc",
2610+ "memchr",
1259]
2611]
1260
2612
1261[[package]]
2613[[package]]
1262−name = "winapi"
2614+name = "winnow"
1263−version = "0.3.9"
2615+version = "0.5.28"
1264source = "registry+https://github.com/rust-lang/crates.io-index"
2616source = "registry+https://github.com/rust-lang/crates.io-index"
1265−checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
2617+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
1266dependencies = [
2618dependencies = [
1267− "winapi-i686-pc-windows-gnu",
2619+ "memchr",
1268− "winapi-x86_64-pc-windows-gnu",
1269]
2620]
1270
2621
1271[[package]]
2622[[package]]
1272−name = "winapi-i686-pc-windows-gnu"
2623+name = "xtask"
2624+version = "23.10.0"
2625+dependencies = [
2626+ "helix-core",
2627+ "helix-loader",
2628+ "helix-term",
2629+ "helix-view",
2630+ "toml",
2631+]
2632+
2633+[[package]]
2634+name = "zerocopy"
1273−version = "0.4.0"
2635+version = "0.7.31"
1274source = "registry+https://github.com/rust-lang/crates.io-index"
2636source = "registry+https://github.com/rust-lang/crates.io-index"
1275−checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2637+checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d"
2638+dependencies = [
2639+ "zerocopy-derive",
2640+]
1276
2641
1277[[package]]
2642[[package]]
1278−name = "winapi-x86_64-pc-windows-gnu"
2643+name = "zerocopy-derive"
1279−version = "0.4.0"
2644+version = "0.7.31"
1280source = "registry+https://github.com/rust-lang/crates.io-index"
2645source = "registry+https://github.com/rust-lang/crates.io-index"
1281−checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2646+checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
2647+dependencies = [
2648+ "proc-macro2",
2649+ "quote",
2650+ "syn 2.0.38",
2651+]
Cargo.toml+42−4
@@ -1,12 +1,50 @@
1[workspace]
1[workspace]
2+resolver = "2"
2members = [
3members = [
3 "helix-core",
4 "helix-core",
4 "helix-view",
5 "helix-view",
5 "helix-term",
6 "helix-term",
6− "helix-syntax",
7+ "helix-tui",
7 "helix-lsp",
8 "helix-lsp",
9+ "helix-event",
10+ "helix-dap",
11+ "helix-loader",
12+ "helix-vcs",
13+ "helix-parsec",
14+ "xtask",
8]
15]
9
16
10−# Build helix-syntax in release mode to make the code path faster in development.
17+default-members = [
11−# [profile.dev.package."helix-syntax"]
18+ "helix-term"
19+]
20+
21+[profile.release]
22+lto = "thin"
23+# debug = true
24+
25+[profile.opt]
26+inherits = "release"
27+lto = "fat"
28+codegen-units = 1
29+# strip = "debuginfo" # TODO: or strip = true
12−# opt-level = 3
30+opt-level = 3
31+
32+[profile.integration]
33+inherits = "test"
34+package.helix-core.opt-level = 2
35+package.helix-tui.opt-level = 2
36+package.helix-term.opt-level = 2
37+
38+[workspace.dependencies]
39+tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" }
40+nucleo = "0.2.0"
41+
42+[workspace.package]
43+version = "23.10.0"
44+edition = "2021"
45+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
46+categories = ["editor"]
47+repository = "https://github.com/helix-editor/helix"
48+homepage = "https://helix-editor.com"
49+license = "MPL-2.0"
50+rust-version = "1.70"
LICENSEnew file+373
@@ -0,0 +1,373 @@
1+Mozilla Public License Version 2.0
2+==================================
3+
4+1. Definitions
5+--------------
6+
7+1.1. "Contributor"
8+ means each individual or legal entity that creates, contributes to
9+ the creation of, or owns Covered Software.
10+
11+1.2. "Contributor Version"
12+ means the combination of the Contributions of others (if any) used
13+ by a Contributor and that particular Contributor's Contribution.
14+
15+1.3. "Contribution"
16+ means Covered Software of a particular Contributor.
17+
18+1.4. "Covered Software"
19+ means Source Code Form to which the initial Contributor has attached
20+ the notice in Exhibit A, the Executable Form of such Source Code
21+ Form, and Modifications of such Source Code Form, in each case
22+ including portions thereof.
23+
24+1.5. "Incompatible With Secondary Licenses"
25+ means
26+
27+ (a) that the initial Contributor has attached the notice described
28+ in Exhibit B to the Covered Software; or
29+
30+ (b) that the Covered Software was made available under the terms of
31+ version 1.1 or earlier of the License, but not also under the
32+ terms of a Secondary License.
33+
34+1.6. "Executable Form"
35+ means any form of the work other than Source Code Form.
36+
37+1.7. "Larger Work"
38+ means a work that combines Covered Software with other material, in
39+ a separate file or files, that is not Covered Software.
40+
41+1.8. "License"
42+ means this document.
43+
44+1.9. "Licensable"
45+ means having the right to grant, to the maximum extent possible,
46+ whether at the time of the initial grant or subsequently, any and
47+ all of the rights conveyed by this License.
48+
49+1.10. "Modifications"
50+ means any of the following:
51+
52+ (a) any file in Source Code Form that results from an addition to,
53+ deletion from, or modification of the contents of Covered
54+ Software; or
55+
56+ (b) any new file in Source Code Form that contains any Covered
57+ Software.
58+
59+1.11. "Patent Claims" of a Contributor
60+ means any patent claim(s), including without limitation, method,
61+ process, and apparatus claims, in any patent Licensable by such
62+ Contributor that would be infringed, but for the grant of the
63+ License, by the making, using, selling, offering for sale, having
64+ made, import, or transfer of either its Contributions or its
65+ Contributor Version.
66+
67+1.12. "Secondary License"
68+ means either the GNU General Public License, Version 2.0, the GNU
69+ Lesser General Public License, Version 2.1, the GNU Affero General
70+ Public License, Version 3.0, or any later versions of those
71+ licenses.
72+
73+1.13. "Source Code Form"
74+ means the form of the work preferred for making modifications.
75+
76+1.14. "You" (or "Your")
77+ means an individual or a legal entity exercising rights under this
78+ License. For legal entities, "You" includes any entity that
79+ controls, is controlled by, or is under common control with You. For
80+ purposes of this definition, "control" means (a) the power, direct
81+ or indirect, to cause the direction or management of such entity,
82+ whether by contract or otherwise, or (b) ownership of more than
83+ fifty percent (50%) of the outstanding shares or beneficial
84+ ownership of such entity.
85+
86+2. License Grants and Conditions
87+--------------------------------
88+
89+2.1. Grants
90+
91+Each Contributor hereby grants You a world-wide, royalty-free,
92+non-exclusive license:
93+
94+(a) under intellectual property rights (other than patent or trademark)
95+ Licensable by such Contributor to use, reproduce, make available,
96+ modify, display, perform, distribute, and otherwise exploit its
97+ Contributions, either on an unmodified basis, with Modifications, or
98+ as part of a Larger Work; and
99+
100+(b) under Patent Claims of such Contributor to make, use, sell, offer
101+ for sale, have made, import, and otherwise transfer either its
102+ Contributions or its Contributor Version.
103+
104+2.2. Effective Date
105+
106+The licenses granted in Section 2.1 with respect to any Contribution
107+become effective for each Contribution on the date the Contributor first
108+distributes such Contribution.
109+
110+2.3. Limitations on Grant Scope
111+
112+The licenses granted in this Section 2 are the only rights granted under
113+this License. No additional rights or licenses will be implied from the
114+distribution or licensing of Covered Software under this License.
115+Notwithstanding Section 2.1(b) above, no patent license is granted by a
116+Contributor:
117+
118+(a) for any code that a Contributor has removed from Covered Software;
119+ or
120+
121+(b) for infringements caused by: (i) Your and any other third party's
122+ modifications of Covered Software, or (ii) the combination of its
123+ Contributions with other software (except as part of its Contributor
124+ Version); or
125+
126+(c) under Patent Claims infringed by Covered Software in the absence of
127+ its Contributions.
128+
129+This License does not grant any rights in the trademarks, service marks,
130+or logos of any Contributor (except as may be necessary to comply with
131+the notice requirements in Section 3.4).
132+
133+2.4. Subsequent Licenses
134+
135+No Contributor makes additional grants as a result of Your choice to
136+distribute the Covered Software under a subsequent version of this
137+License (see Section 10.2) or under the terms of a Secondary License (if
138+permitted under the terms of Section 3.3).
139+
140+2.5. Representation
141+
142+Each Contributor represents that the Contributor believes its
143+Contributions are its original creation(s) or it has sufficient rights
144+to grant the rights to its Contributions conveyed by this License.
145+
146+2.6. Fair Use
147+
148+This License is not intended to limit any rights You have under
149+applicable copyright doctrines of fair use, fair dealing, or other
150+equivalents.
151+
152+2.7. Conditions
153+
154+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155+in Section 2.1.
156+
157+3. Responsibilities
158+-------------------
159+
160+3.1. Distribution of Source Form
161+
162+All distribution of Covered Software in Source Code Form, including any
163+Modifications that You create or to which You contribute, must be under
164+the terms of this License. You must inform recipients that the Source
165+Code Form of the Covered Software is governed by the terms of this
166+License, and how they can obtain a copy of this License. You may not
167+attempt to alter or restrict the recipients' rights in the Source Code
168+Form.
169+
170+3.2. Distribution of Executable Form
171+
172+If You distribute Covered Software in Executable Form then:
173+
174+(a) such Covered Software must also be made available in Source Code
175+ Form, as described in Section 3.1, and You must inform recipients of
176+ the Executable Form how they can obtain a copy of such Source Code
177+ Form by reasonable means in a timely manner, at a charge no more
178+ than the cost of distribution to the recipient; and
179+
180+(b) You may distribute such Executable Form under the terms of this
181+ License, or sublicense it under different terms, provided that the
182+ license for the Executable Form does not attempt to limit or alter
183+ the recipients' rights in the Source Code Form under this License.
184+
185+3.3. Distribution of a Larger Work
186+
187+You may create and distribute a Larger Work under terms of Your choice,
188+provided that You also comply with the requirements of this License for
189+the Covered Software. If the Larger Work is a combination of Covered
190+Software with a work governed by one or more Secondary Licenses, and the
191+Covered Software is not Incompatible With Secondary Licenses, this
192+License permits You to additionally distribute such Covered Software
193+under the terms of such Secondary License(s), so that the recipient of
194+the Larger Work may, at their option, further distribute the Covered
195+Software under the terms of either this License or such Secondary
196+License(s).
197+
198+3.4. Notices
199+
200+You may not remove or alter the substance of any license notices
201+(including copyright notices, patent notices, disclaimers of warranty,
202+or limitations of liability) contained within the Source Code Form of
203+the Covered Software, except that You may alter any license notices to
204+the extent required to remedy known factual inaccuracies.
205+
206+3.5. Application of Additional Terms
207+
208+You may choose to offer, and to charge a fee for, warranty, support,
209+indemnity or liability obligations to one or more recipients of Covered
210+Software. However, You may do so only on Your own behalf, and not on
211+behalf of any Contributor. You must make it absolutely clear that any
212+such warranty, support, indemnity, or liability obligation is offered by
213+You alone, and You hereby agree to indemnify every Contributor for any
214+liability incurred by such Contributor as a result of warranty, support,
215+indemnity or liability terms You offer. You may include additional
216+disclaimers of warranty and limitations of liability specific to any
217+jurisdiction.
218+
219+4. Inability to Comply Due to Statute or Regulation
220+---------------------------------------------------
221+
222+If it is impossible for You to comply with any of the terms of this
223+License with respect to some or all of the Covered Software due to
224+statute, judicial order, or regulation then You must: (a) comply with
225+the terms of this License to the maximum extent possible; and (b)
226+describe the limitations and the code they affect. Such description must
227+be placed in a text file included with all distributions of the Covered
228+Software under this License. Except to the extent prohibited by statute
229+or regulation, such description must be sufficiently detailed for a
230+recipient of ordinary skill to be able to understand it.
231+
232+5. Termination
233+--------------
234+
235+5.1. The rights granted under this License will terminate automatically
236+if You fail to comply with any of its terms. However, if You become
237+compliant, then the rights granted under this License from a particular
238+Contributor are reinstated (a) provisionally, unless and until such
239+Contributor explicitly and finally terminates Your grants, and (b) on an
240+ongoing basis, if such Contributor fails to notify You of the
241+non-compliance by some reasonable means prior to 60 days after You have
242+come back into compliance. Moreover, Your grants from a particular
243+Contributor are reinstated on an ongoing basis if such Contributor
244+notifies You of the non-compliance by some reasonable means, this is the
245+first time You have received notice of non-compliance with this License
246+from such Contributor, and You become compliant prior to 30 days after
247+Your receipt of the notice.
248+
249+5.2. If You initiate litigation against any entity by asserting a patent
250+infringement claim (excluding declaratory judgment actions,
251+counter-claims, and cross-claims) alleging that a Contributor Version
252+directly or indirectly infringes any patent, then the rights granted to
253+You by any and all Contributors for the Covered Software under Section
254+2.1 of this License shall terminate.
255+
256+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257+end user license agreements (excluding distributors and resellers) which
258+have been validly granted by You or Your distributors under this License
259+prior to termination shall survive termination.
260+
261+************************************************************************
262+* *
263+* 6. Disclaimer of Warranty *
264+* ------------------------- *
265+* *
266+* Covered Software is provided under this License on an "as is" *
267+* basis, without warranty of any kind, either expressed, implied, or *
268+* statutory, including, without limitation, warranties that the *
269+* Covered Software is free of defects, merchantable, fit for a *
270+* particular purpose or non-infringing. The entire risk as to the *
271+* quality and performance of the Covered Software is with You. *
272+* Should any Covered Software prove defective in any respect, You *
273+* (not any Contributor) assume the cost of any necessary servicing, *
274+* repair, or correction. This disclaimer of warranty constitutes an *
275+* essential part of this License. No use of any Covered Software is *
276+* authorized under this License except under this disclaimer. *
277+* *
278+************************************************************************
279+
280+************************************************************************
281+* *
282+* 7. Limitation of Liability *
283+* -------------------------- *
284+* *
285+* Under no circumstances and under no legal theory, whether tort *
286+* (including negligence), contract, or otherwise, shall any *
287+* Contributor, or anyone who distributes Covered Software as *
288+* permitted above, be liable to You for any direct, indirect, *
289+* special, incidental, or consequential damages of any character *
290+* including, without limitation, damages for lost profits, loss of *
291+* goodwill, work stoppage, computer failure or malfunction, or any *
292+* and all other commercial damages or losses, even if such party *
293+* shall have been informed of the possibility of such damages. This *
294+* limitation of liability shall not apply to liability for death or *
295+* personal injury resulting from such party's negligence to the *
296+* extent applicable law prohibits such limitation. Some *
297+* jurisdictions do not allow the exclusion or limitation of *
298+* incidental or consequential damages, so this exclusion and *
299+* limitation may not apply to You. *
300+* *
301+************************************************************************
302+
303+8. Litigation
304+-------------
305+
306+Any litigation relating to this License may be brought only in the
307+courts of a jurisdiction where the defendant maintains its principal
308+place of business and such litigation shall be governed by laws of that
309+jurisdiction, without reference to its conflict-of-law provisions.
310+Nothing in this Section shall prevent a party's ability to bring
311+cross-claims or counter-claims.
312+
313+9. Miscellaneous
314+----------------
315+
316+This License represents the complete agreement concerning the subject
317+matter hereof. If any provision of this License is held to be
318+unenforceable, such provision shall be reformed only to the extent
319+necessary to make it enforceable. Any law or regulation which provides
320+that the language of a contract shall be construed against the drafter
321+shall not be used to construe this License against a Contributor.
322+
323+10. Versions of the License
324+---------------------------
325+
326+10.1. New Versions
327+
328+Mozilla Foundation is the license steward. Except as provided in Section
329+10.3, no one other than the license steward has the right to modify or
330+publish new versions of this License. Each version will be given a
331+distinguishing version number.
332+
333+10.2. Effect of New Versions
334+
335+You may distribute the Covered Software under the terms of the version
336+of the License under which You originally received the Covered Software,
337+or under the terms of any subsequent version published by the license
338+steward.
339+
340+10.3. Modified Versions
341+
342+If you create software not governed by this License, and you want to
343+create a new license for such software, you may create and use a
344+modified version of this License if you rename the license and remove
345+any references to the name of the license steward (except to note that
346+such modified license differs from this License).
347+
348+10.4. Distributing Source Code Form that is Incompatible With Secondary
349+Licenses
350+
351+If You choose to distribute Source Code Form that is Incompatible With
352+Secondary Licenses under the terms of this version of the License, the
353+notice described in Exhibit B of this License must be attached.
354+
355+Exhibit A - Source Code Form License Notice
356+-------------------------------------------
357+
358+ This Source Code Form is subject to the terms of the Mozilla Public
359+ License, v. 2.0. If a copy of the MPL was not distributed with this
360+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
361+
362+If it is not possible or desirable to put the notice in a particular
363+file, then You may include the notice in a location (such as a LICENSE
364+file in a relevant directory) where a recipient would be likely to look
365+for such a notice.
366+
367+You may add additional accurate notices of copyright ownership.
368+
369+Exhibit B - "Incompatible With Secondary Licenses" Notice
370+---------------------------------------------------------
371+
372+ This Source Code Form is "Incompatible With Secondary Licenses", as
373+ defined by the Mozilla Public License, v. 2.0.
README.md+60−85
@@ -1,89 +1,64 @@
1−# Helix
1+<div align="center">
2
2
3−| Crate | Description |
3+<h1>
4−| ----------- | ----------- |
4+<picture>
5−| helix-core | Core editing primitives, functional. |
5+ <source media="(prefers-color-scheme: dark)" srcset="logo_dark.svg">
6−| helix-syntax | Tree-sitter grammars |
6+ <source media="(prefers-color-scheme: light)" srcset="logo_light.svg">
7−| helix-view | UI abstractions for use in backends, imperative shell. |
7+ <img alt="Helix" height="128" src="logo_light.svg">
8−| helix-term | Terminal UI |
8+</picture>
9+</h1>
10+
11+[![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions)
12+[![GitHub Release](https://img.shields.io/github/v/release/helix-editor/helix)](https://github.com/helix-editor/helix/releases/latest)
13+[![Documentation](https://shields.io/badge/-documentation-452859)](https://docs.helix-editor.com/)
14+[![GitHub contributors](https://img.shields.io/github/contributors/helix-editor/helix)](https://github.com/helix-editor/helix/graphs/contributors)
15+[![Matrix Space](https://img.shields.io/matrix/helix-community:matrix.org)](https://matrix.to/#/#helix-community:matrix.org)
16+
17+</div>
18+
19+![Screenshot](./screenshot.png)
20+
21+A [Kakoune](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/neovim) inspired editor, written in Rust.
22+
23+The editing model is very heavily based on Kakoune; during development I found
24+myself agreeing with most of Kakoune's design decisions.
25+
26+For more information, see the [website](https://helix-editor.com) or
27+[documentation](https://docs.helix-editor.com/).
28+
29+All shortcuts/keymaps can be found [in the documentation on the website](https://docs.helix-editor.com/keymap.html).
30+
31+[Troubleshooting](https://github.com/helix-editor/helix/wiki/Troubleshooting)
32+
33+# Features
34+
35+- Vim-like modal editing
36+- Multiple selections
37+- Built-in language server support
38+- Smart, incremental syntax highlighting and code editing via tree-sitter
39+
40+It's a terminal-based editor first, but I'd like to explore a custom renderer
41+(similar to Emacs) in wgpu or skulpin.
42+
43+Note: Only certain languages have indentation definitions at the moment. Check
44+`runtime/queries/<lang>/` for `indents.scm`.
9
45
10# Installation
46# Installation
11
47
12−```
48+[Installation documentation](https://docs.helix-editor.com/install.html).
49+
50+[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
51+
52+# Contributing
53+
54+Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
55+
56+# Getting help
57+
13−git clone --depth 1 --recurse-submodules -j8 https://github.com/helix-editor/helix
58+Your question might already be answered on the [FAQ](https://github.com/helix-editor/helix/wiki/FAQ).
14−cd helix
59+
15−cargo install --path helix-term
60+Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-community:matrix.org) (make sure to join `#helix-editor:matrix.org` if you're on a client that doesn't support Matrix Spaces yet).
16−```
61+
17−
62+# Credits
18−This will install the `hx` binary to `$HOME/.cargo/bin`.
63+
19−
64+Thanks to [@jakenvac](https://github.com/jakenvac) for designing the logo!
20−# Notes
21−
22−- server-client architecture via gRPC, UI separate from core
23−- multi cursor based editing and slicing
24−- WASM based plugins (builtin LSP & fuzzy file finder)
25−
26−Structure similar to codemirror:
27−
28−- text (ropes)
29−- column utils, stuff like tab aware (row, col) -> char pos translation
30−- word/grapheme/code point utils and iterators
31−- state
32−- transactions
33− - changes
34− - annotations (time changed etc)
35− - state effects
36− - additional editor state as facets
37−- snapshots as an async view into current state
38−- selections { anchor (nonmoving), head (moving) from/to } -> SelectionSet with a primary
39− - cursor is just a single range selection
40−- markers
41− track a position inside text that synchronizes with edits
42−- { doc, selection, update(), splice, changes(), facets, tabSize, identUnit, lineSeparator, changeFilter/transactionFilter to modify stuff before }
43−- view (actual UI)
44−- renders via termwiz
45−- viewport(Lines) -> what's actually visible
46−- extend the view via Decorations (inline styling) or Components (UI)
47− - mark / wieget / line / replace decoration
48−- commands (transform state)
49−- movement
50−- selection extension
51−- deletion
52−- indentation
53−- keymap (maps keys to commands)
54−- history (undo tree via immutable ropes)
55−- undoes transactions
56−- invert changes (generates a revert)
57−- (collab mode)
58−- gutter (line numbers, diagnostic marker, etc) -> ties into UI components
59−- rangeset/span -> mappable over changes (can be a marker primitive?)
60−- syntax (treesitter)
61−- indentation strategies
62−- fold
63−- selections (select mode/multiselect)
64−- matchbrackets
65−- closebrackets
66−- special-chars (shows dots etc for specials)
67−- panel (for UI: file pickers, search dialogs, etc)
68−- tooltip (for UI)
69−- search (regex? pcre)
70−- lint (async linters)
71−- lsp
72−- highlight (?)
73−- stream-syntax
74−- autocomplete
75−- comment (gc, etc for auto commenting)
76−- snippets
77−- terminal mode?
78−
79−- plugins can contain more commands/ui abstractions to use elsewhere
80−- languageData as presets for each language (syntax, indent, comment, etc)
81−
82−Vim stuff:
83−- motions/operators/text objects
84−- full visual mode
85−- macros
86−- jump lists
87−- marks
88−- yank/paste
89−- conceal for markdown markers, etc
TODO.mddeleted−18
@@ -1,18 +0,0 @@
1−helper methods: iterate over selection spans in the document.
2−
3−- Implement marks (superset of Selection/Range)
4−- Implement style configs, tab settings
5−- Visual tab width
6−- Refactor tree-sitter-highlight to work like the atom one, recomputing partial tree updates.
7−- Only render highlights on screen
8−- proper selection rendering
9−- Undo tree
10−- selection mode
11−- key sequence shortcuts (gg etc)
12−- syntax errors highlight query
13−
14−- UI work: command line
15−- UI work: tab popup on command line
16−- UI work: completion popup
17−- UI work: floating pane
18−
base16_theme.tomlnew file+51
@@ -0,0 +1,51 @@
1+# Author: NNB <nnbnh@protonmail.com>
2+
3+"ui.menu" = { fg = "black", bg = "white" }
4+"ui.menu.selected" = { modifiers = ["reversed"] }
5+"ui.linenr" = { fg = "gray", bg = "black" }
6+"ui.popup" = { modifiers = ["reversed"] }
7+"ui.linenr.selected" = { fg = "white", bg = "black", modifiers = ["bold"] }
8+"ui.selection" = { fg = "black", bg = "blue" }
9+"ui.selection.primary" = { fg = "white", bg = "blue" }
10+"ui.text.inactive" = { fg = "gray" }
11+"comment" = { fg = "gray" }
12+"ui.statusline" = { fg = "black", bg = "white" }
13+"ui.statusline.inactive" = { fg = "gray", bg = "white" }
14+"ui.cursor" = { fg = "white", modifiers = ["reversed"] }
15+"variable" = "red"
16+"constant.numeric" = "yellow"
17+"constant" = "yellow"
18+"attributes" = "yellow"
19+"type" = "yellow"
20+"ui.cursor.match" = { fg = "yellow", modifiers = ["underlined"] }
21+"string" = "green"
22+"variable.other.member" = "green"
23+"constant.character.escape" = "cyan"
24+"function" = "blue"
25+"constructor" = "blue"
26+"special" = "blue"
27+"keyword" = "magenta"
28+"label" = "magenta"
29+"namespace" = "magenta"
30+"ui.help" = { fg = "white", bg = "black" }
31+
32+"markup.heading" = "blue"
33+"markup.list" = "red"
34+"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
35+"markup.italic" = { fg = "magenta", modifiers = ["italic"] }
36+"markup.link.url" = { fg = "yellow", modifiers = ["underlined"] }
37+"markup.link.text" = "red"
38+"markup.quote" = "cyan"
39+"markup.raw" = "green"
40+
41+"diff.plus" = "green"
42+"diff.delta" = "yellow"
43+"diff.minus" = "red"
44+
45+"diagnostic" = { modifiers = ["underlined"] }
46+"ui.gutter" = { bg = "black" }
47+"info" = "blue"
48+"hint" = "gray"
49+"debug" = "gray"
50+"warning" = "yellow"
51+"error" = "red"
book/.gitignorenew file+1
@@ -0,0 +1 @@
1+book
book/book.tomlnew file+16
@@ -0,0 +1,16 @@
1+[book]
2+authors = ["Blaž Hrastnik"]
3+language = "en"
4+multilingual = false
5+src = "src"
6+
7+[output.html]
8+cname = "docs.helix-editor.com"
9+default-theme = "colibri"
10+preferred-dark-theme = "colibri"
11+git-repository-url = "https://github.com/helix-editor/helix"
12+edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
13+additional-css = ["custom.css"]
14+
15+[output.html.search]
16+use-boolean-and = true
book/custom.cssnew file+231
@@ -0,0 +1,231 @@
1+html {
2+ font-family: "Inter", sans-serif;
3+}
4+
5+.sidebar .sidebar-scrollbox {
6+ padding: 0;
7+}
8+
9+.chapter {
10+ margin: 0.25rem 0;
11+}
12+
13+.chapter li.chapter-item {
14+ line-height: initial;
15+ margin: 0;
16+ padding: 1rem 1.5rem;
17+}
18+
19+.chapter .section li.chapter-item {
20+ line-height: inherit;
21+ padding: .5rem .5rem 0 .5rem;
22+}
23+
24+.content {
25+ overflow-y: auto;
26+ padding: 0 15px;
27+ padding-bottom: 50px;
28+}
29+
30+/* 2 1.75 1.5 1.25 1 .875 */
31+.content h1 { font-size: 2em }
32+.content h2 { font-size: 1.75em }
33+.content h3 { font-size: 1.5em }
34+.content h4 { font-size: 1.25em }
35+.content h5 { font-size: 1em }
36+.content h6 { font-size: .875em }
37+
38+.content h1,
39+.content h2,
40+.content h3,
41+.content h4 {
42+ font-weight: 500;
43+ margin-top: 1.275em;
44+ margin-bottom: .875em;
45+}
46+
47+.content p,
48+.content ol,
49+.content ul,
50+.content table {
51+ margin-top: 0;
52+ margin-bottom: .875em;
53+}
54+
55+.content ul li {
56+ margin-bottom: .25rem;
57+}
58+
59+.content ul {
60+ list-style-type: square;
61+}
62+
63+.content ul ul,
64+.content ol ul {
65+ margin-bottom: .5rem;
66+}
67+
68+.content li p {
69+ margin-bottom: .5em;
70+}
71+
72+blockquote {
73+ margin: 1.5rem 0;
74+ padding: 1rem 1.5rem;
75+ color: var(--fg);
76+ opacity: .9;
77+ background-color: var(--quote-bg);
78+ border-left: 4px solid var(--quote-border);
79+ border-top: none;
80+ border-bottom: none;
81+}
82+
83+blockquote *:last-child {
84+ margin-bottom: 0;
85+}
86+
87+table {
88+ width: 100%;
89+}
90+
91+table thead th {
92+ padding: .75rem;
93+ text-align: left;
94+ font-weight: 500;
95+ line-height: 1.5;
96+ width: auto;
97+}
98+
99+table td {
100+ padding: .75rem;
101+ border: none;
102+}
103+
104+table thead tr {
105+ border: none;
106+ border-bottom: 2px var(--table-border-color) solid;
107+}
108+
109+table tbody tr {
110+ border-bottom: 1px var(--table-border-line) solid;
111+}
112+
113+table tbody tr:nth-child(2n) {
114+ background: unset;
115+}
116+
117+pre code.hljs {
118+ display: block;
119+ overflow-x: auto;
120+ padding: 1em;
121+}
122+
123+code.hljs {
124+ padding: 3px 5px;
125+}
126+
127+.colibri {
128+ --bg: #3b224c;
129+ --fg: #bcbdd0;
130+ --heading-fg: #fff;
131+
132+ --sidebar-bg: #281733;
133+ --sidebar-fg: #c8c9db;
134+ --sidebar-non-existent: #505274;
135+ --sidebar-active: #a4a0e8;
136+ --sidebar-spacer: #2d334f;
137+
138+ --scrollbar: var(--sidebar-fg);
139+
140+ --icons: #737480;
141+ --icons-hover: #b7b9cc;
142+
143+ /* --links: #a4a0e8; */
144+ --links: #ECCDBA;
145+
146+ --inline-code-color: hsl(48.7, 7.8%, 70%);
147+
148+ --theme-popup-bg: #161923;
149+ --theme-popup-border: #737480;
150+ --theme-hover: rgba(0, 0, 0, .2);
151+
152+ --quote-bg: #281733;
153+ --quote-border: hsl(226, 15%, 22%);
154+
155+ --table-border-color: hsl(226, 23%, 76%);
156+ --table-header-bg: hsla(226, 23%, 31%, 0);
157+ --table-alternate-bg: hsl(226, 23%, 14%);
158+ --table-border-line: hsla(201deg, 20%, 92%, 0.2);
159+
160+ --searchbar-border-color: #aaa;
161+ --searchbar-bg: #aeaec6;
162+ --searchbar-fg: #000;
163+ --searchbar-shadow-color: #aaa;
164+ --searchresults-header-fg: #5f5f71;
165+ --searchresults-border-color: #5c5c68;
166+ --searchresults-li-bg: #242430;
167+ --search-mark-bg: #a2cff5;
168+}
169+
170+.colibri .content .header {
171+ color: #fff;
172+}
173+
174+/* highlight.js theme, :where() is used to avoid increasing specificity */
175+
176+:where(.colibri) .hljs {
177+ background: #2f1e2e;
178+ color: #a39e9b;
179+}
180+
181+:where(.colibri) .hljs-comment,
182+:where(.colibri) .hljs-quote {
183+ color: #8d8687;
184+}
185+
186+:where(.colibri) .hljs-link,
187+:where(.colibri) .hljs-meta,
188+:where(.colibri) .hljs-name,
189+:where(.colibri) .hljs-regexp,
190+:where(.colibri) .hljs-selector-class,
191+:where(.colibri) .hljs-selector-id,
192+:where(.colibri) .hljs-tag,
193+:where(.colibri) .hljs-template-variable,
194+:where(.colibri) .hljs-variable {
195+ color: #ef6155;
196+}
197+
198+:where(.colibri) .hljs-built_in,
199+:where(.colibri) .hljs-deletion,
200+:where(.colibri) .hljs-literal,
201+:where(.colibri) .hljs-number,
202+:where(.colibri) .hljs-params,
203+:where(.colibri) .hljs-type {
204+ color: #f99b15;
205+}
206+
207+:where(.colibri) .hljs-attribute,
208+:where(.colibri) .hljs-section,
209+:where(.colibri) .hljs-title {
210+ color: #fec418;
211+}
212+
213+:where(.colibri) .hljs-addition,
214+:where(.colibri) .hljs-bullet,
215+:where(.colibri) .hljs-string,
216+:where(.colibri) .hljs-symbol {
217+ color: #48b685;
218+}
219+
220+:where(.colibri) .hljs-keyword,
221+:where(.colibri) .hljs-selector-tag {
222+ color: #815ba4;
223+}
224+
225+:where(.colibri) .hljs-emphasis {
226+ font-style: italic;
227+}
228+
229+:where(.colibri) .hljs-strong {
230+ font-weight: 700;
231+}
book/src/SUMMARY.mdnew file+19
@@ -0,0 +1,19 @@
1+# Summary
2+
3+[Helix](./title-page.md)
4+
5+- [Installation](./install.md)
6+- [Usage](./usage.md)
7+ - [Keymap](./keymap.md)
8+ - [Commands](./commands.md)
9+ - [Language support](./lang-support.md)
10+- [Migrating from Vim](./from-vim.md)
11+- [Configuration](./configuration.md)
12+ - [Themes](./themes.md)
13+ - [Key remapping](./remapping.md)
14+ - [Languages](./languages.md)
15+- [Guides](./guides/README.md)
16+ - [Adding languages](./guides/adding_languages.md)
17+ - [Adding textobject queries](./guides/textobject.md)
18+ - [Adding indent queries](./guides/indent.md)
19+ - [Adding injection queries](./guides/injection.md)
book/src/commands.mdnew file+5
@@ -0,0 +1,5 @@
1+# Commands
2+
3+Command mode can be activated by pressing `:`. The built-in commands are:
4+
5+{{#include ./generated/typable-cmd.md}}
book/src/configuration.mdnew file+376
@@ -0,0 +1,376 @@
1+# Configuration
2+
3+To override global configuration parameters, create a `config.toml` file located in your config directory:
4+
5+- Linux and Mac: `~/.config/helix/config.toml`
6+- Windows: `%AppData%\helix\config.toml`
7+
8+> 💡 You can easily open the config file by typing `:config-open` within Helix normal mode.
9+
10+Example config:
11+
12+```toml
13+theme = "onedark"
14+
15+[editor]
16+line-number = "relative"
17+mouse = false
18+
19+[editor.cursor-shape]
20+insert = "bar"
21+normal = "block"
22+select = "underline"
23+
24+[editor.file-picker]
25+hidden = false
26+```
27+
28+You can use a custom configuration file by specifying it with the `-c` or
29+`--config` command line argument, for example `hx -c path/to/custom-config.toml`.
30+Additionally, you can reload the configuration file by sending the USR1
31+signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
32+
33+Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
34+Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.
35+
36+## Editor
37+
38+### `[editor]` Section
39+
40+| Key | Description | Default |
41+|--|--|---------|
42+| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling | `5` |
43+| `mouse` | Enable mouse mode | `true` |
44+| `middle-click-paste` | Middle click paste support | `true` |
45+| `scroll-lines` | Number of lines to scroll per scroll wheel step | `3` |
46+| `shell` | Shell to use when running external commands | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
47+| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers | `absolute` |
48+| `cursorline` | Highlight all lines with a cursor | `false` |
49+| `cursorcolumn` | Highlight all columns with a cursor | `false` |
50+| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
51+| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
52+| `auto-format` | Enable automatic formatting on save | `true` |
53+| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
54+| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `250` |
55+| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
56+| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
57+| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
58+| `auto-info` | Whether to display info boxes | `true` |
59+| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` |
60+| `undercurl` | Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative | `false` |
61+| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
62+| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
63+| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
64+| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
65+| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
66+| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
67+| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
68+| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
69+| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
70+
71+### `[editor.statusline]` Section
72+
73+Allows configuring the statusline at the bottom of the editor.
74+
75+The configuration distinguishes between three areas of the status line:
76+
77+`[ ... ... LEFT ... ... | ... ... ... ... CENTER ... ... ... ... | ... ... RIGHT ... ... ]`
78+
79+Statusline elements can be defined as follows:
80+
81+```toml
82+[editor.statusline]
83+left = ["mode", "spinner"]
84+center = ["file-name"]
85+right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
86+separator = "│"
87+mode.normal = "NORMAL"
88+mode.insert = "INSERT"
89+mode.select = "SELECT"
90+```
91+The `[editor.statusline]` key takes the following sub-keys:
92+
93+| Key | Description | Default |
94+| --- | --- | --- |
95+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
96+| `center` | A list of elements aligned to the middle of the statusline | `[]` |
97+| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
98+| `separator` | The character used to separate elements in the statusline | `"│"` |
99+| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
100+| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
101+| `mode.select` | The text shown in the `mode` element for select mode | `"SEL"` |
102+
103+The following statusline elements can be configured:
104+
105+| Key | Description |
106+| ------ | ----------- |
107+| `mode` | The current editor mode (`mode.normal`/`mode.insert`/`mode.select`) |
108+| `spinner` | A progress spinner indicating LSP activity |
109+| `file-name` | The path/name of the opened file |
110+| `file-base-name` | The basename of the opened file |
111+| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
112+| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
113+| `file-line-ending` | The file line endings (CRLF or LF) |
114+| `read-only-indicator` | An indicator that shows `[readonly]` when a file cannot be written |
115+| `total-line-numbers` | The total line numbers of the opened file |
116+| `file-type` | The type of the opened file |
117+| `diagnostics` | The number of warnings and/or errors |
118+| `workspace-diagnostics` | The number of warnings and/or errors on workspace |
119+| `selections` | The number of active selections |
120+| `primary-selection-length` | The number of characters currently in primary selection |
121+| `position` | The cursor position |
122+| `position-percentage` | The cursor position as a percentage of the total number of lines |
123+| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
124+| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
125+| `version-control` | The current branch name or detached commit hash of the opened workspace |
126+| `register` | The current selected register |
127+
128+### `[editor.lsp]` Section
129+
130+| Key | Description | Default |
131+| --- | ----------- | ------- |
132+| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
133+| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
134+| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
135+| `display-inlay-hints` | Display inlay hints[^2] | `false` |
136+| `display-signature-help-docs` | Display docs under signature help popup | `true` |
137+| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
138+| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
139+
140+[^1]: By default, a progress spinner is shown in the statusline beside the file path.
141+
142+[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix. Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances. Please report any bugs you see so we can fix them!
143+
144+### `[editor.cursor-shape]` Section
145+
146+Defines the shape of cursor in each mode.
147+Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
148+
149+> 💡 Due to limitations of the terminal environment, only the primary cursor can
150+> change shape.
151+
152+| Key | Description | Default |
153+| --- | ----------- | ------- |
154+| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
155+| `insert` | Cursor shape in [insert mode][insert mode] | `block` |
156+| `select` | Cursor shape in [select mode][select mode] | `block` |
157+
158+[normal mode]: ./keymap.md#normal-mode
159+[insert mode]: ./keymap.md#insert-mode
160+[select mode]: ./keymap.md#select--extend-mode
161+
162+### `[editor.file-picker]` Section
163+
164+Set options for file picker and global search. Ignoring a file means it is
165+not visible in the Helix file picker and global search.
166+
167+All git related options are only enabled in a git repository.
168+
169+| Key | Description | Default |
170+|--|--|---------|
171+|`hidden` | Enables ignoring hidden files | `true`
172+|`follow-symlinks` | Follow symlinks instead of ignoring them | `true`
173+|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | `true`
174+|`parents` | Enables reading ignore files from parent directories | `true`
175+|`ignore` | Enables reading `.ignore` files | `true`
176+|`git-ignore` | Enables reading `.gitignore` files | `true`
177+|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludesfile` option | `true`
178+|`git-exclude` | Enables reading `.git/info/exclude` files | `true`
179+|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
180+
181+Ignore files can be placed locally as `.ignore` or put in your home directory as `~/.ignore`. They support the usual ignore and negative ignore (unignore) rules used in `.gitignore` files.
182+
183+Additionally, you can use Helix-specific ignore files by creating a local `.helix/ignore` file in the current workspace or a global `ignore` file located in your Helix config directory:
184+- Linux and Mac: `~/.config/helix/ignore`
185+- Windows: `%AppData%\helix\ignore`
186+
187+Example:
188+
189+```ini
190+# unignore in file picker and global search
191+!.github/
192+!.gitignore
193+!.gitattributes
194+```
195+
196+### `[editor.auto-pairs]` Section
197+
198+Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
199+simple boolean value, or a specific mapping of pairs of single characters.
200+
201+To disable auto-pairs altogether, set `auto-pairs` to `false`:
202+
203+```toml
204+[editor]
205+auto-pairs = false # defaults to `true`
206+```
207+
208+The default pairs are <code>(){}[]''""``</code>, but these can be customized by
209+setting `auto-pairs` to a TOML table:
210+
211+```toml
212+[editor.auto-pairs]
213+'(' = ')'
214+'{' = '}'
215+'[' = ']'
216+'"' = '"'
217+'`' = '`'
218+'<' = '>'
219+```
220+
221+Additionally, this setting can be used in a language config. Unless
222+the editor setting is `false`, this will override the editor config in
223+documents with this language.
224+
225+Example `languages.toml` that adds <> and removes ''
226+
227+```toml
228+[[language]]
229+name = "rust"
230+
231+[language.auto-pairs]
232+'(' = ')'
233+'{' = '}'
234+'[' = ']'
235+'"' = '"'
236+'`' = '`'
237+'<' = '>'
238+```
239+
240+### `[editor.search]` Section
241+
242+Search specific options.
243+
244+| Key | Description | Default |
245+|--|--|---------|
246+| `smart-case` | Enable smart case regex searching (case-insensitive unless pattern contains upper case characters) | `true` |
247+| `wrap-around`| Whether the search should wrap after depleting the matches | `true` |
248+
249+### `[editor.whitespace]` Section
250+
251+Options for rendering whitespace with visible characters. Use `:set whitespace.render all` to temporarily enable visible whitespace.
252+
253+| Key | Description | Default |
254+|-----|-------------|---------|
255+| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `tab`, and `newline` | `"none"` |
256+| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `newline` or `tabpad` | See example below |
257+
258+Example
259+
260+```toml
261+[editor.whitespace]
262+render = "all"
263+# or control each character
264+[editor.whitespace.render]
265+space = "all"
266+tab = "all"
267+newline = "none"
268+
269+[editor.whitespace.characters]
270+space = "·"
271+nbsp = "⍽"
272+tab = "→"
273+newline = "⏎"
274+tabpad = "·" # Tabs will look like "→···" (depending on tab width)
275+```
276+
277+### `[editor.indent-guides]` Section
278+
279+Options for rendering vertical indent guides.
280+
281+| Key | Description | Default |
282+| --- | --- | --- |
283+| `render` | Whether to render indent guides | `false` |
284+| `character` | Literal character to use for rendering the indent guide | `│` |
285+| `skip-levels` | Number of indent levels to skip | `0` |
286+
287+Example:
288+
289+```toml
290+[editor.indent-guides]
291+render = true
292+character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
293+skip-levels = 1
294+```
295+
296+### `[editor.gutters]` Section
297+
298+For simplicity, `editor.gutters` accepts an array of gutter types, which will
299+use default settings for all gutter components.
300+
301+```toml
302+[editor]
303+gutters = ["diff", "diagnostics", "line-numbers", "spacer"]
304+```
305+
306+To customize the behavior of gutters, the `[editor.gutters]` section must
307+be used. This section contains top level settings, as well as settings for
308+specific gutter components as subsections.
309+
310+| Key | Description | Default |
311+| --- | --- | --- |
312+| `layout` | A vector of gutters to display | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
313+
314+Example:
315+
316+```toml
317+[editor.gutters]
318+layout = ["diff", "diagnostics", "line-numbers", "spacer"]
319+```
320+
321+#### `[editor.gutters.line-numbers]` Section
322+
323+Options for the line number gutter
324+
325+| Key | Description | Default |
326+| --- | --- | --- |
327+| `min-width` | The minimum number of characters to use | `3` |
328+
329+Example:
330+
331+```toml
332+[editor.gutters.line-numbers]
333+min-width = 1
334+```
335+
336+#### `[editor.gutters.diagnostics]` Section
337+
338+Currently unused
339+
340+#### `[editor.gutters.diff]` Section
341+
342+Currently unused
343+
344+#### `[editor.gutters.spacer]` Section
345+
346+Currently unused
347+
348+### `[editor.soft-wrap]` Section
349+
350+Options for soft wrapping lines that exceed the view width:
351+
352+| Key | Description | Default |
353+| --- | --- | --- |
354+| `enable` | Whether soft wrapping is enabled. | `false` |
355+| `max-wrap` | Maximum free space left at the end of the line. | `20` |
356+| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line. | `40` |
357+| `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` |
358+| `wrap-at-text-width` | Soft wrap at `text-width` instead of using the full viewport size. | `false` |
359+
360+Example:
361+
362+```toml
363+[editor.soft-wrap]
364+enable = true
365+max-wrap = 25 # increase value to reduce forced mid-word wrapping
366+max-indent-retain = 0
367+wrap-indicator = "" # set wrap-indicator to "" to hide it
368+```
369+
370+### `[editor.smart-tab]` Section
371+
372+
373+| Key | Description | Default |
374+|------------|-------------|---------|
375+| `enable` | If set to true, then when the cursor is in a position with non-whitespace to its left, instead of inserting a tab, it will run `move_parent_node_end`. If there is only whitespace to the left, then it inserts a tab as normal. With the default bindings, to explicitly insert a tab character, press Shift-tab. | `true` |
376+| `supersede-menu` | Normally, when a menu is on screen, such as when auto complete is triggered, the tab key is bound to cycling through the items. This means when menus are on screen, one cannot use the tab key to trigger the `smart-tab` command. If this option is set to true, the `smart-tab` command always takes precedence, which means one cannot use the tab key to cycle through menu items. One of the other bindings must be used instead, such as arrow keys or `C-n`/`C-p`. | `false` |
book/src/from-vim.mdnew file+12
@@ -0,0 +1,12 @@
1+# Migrating from Vim
2+
3+Helix's editing model is strongly inspired from Vim and Kakoune, and a notable
4+difference from Vim (and the most striking similarity to Kakoune) is that Helix
5+follows the `selection → action` model. This means that whatever you are
6+going to act on (a word, a paragraph, a line, etc.) is selected first and the
7+action itself (delete, change, yank, etc.) comes second. A cursor is simply a
8+single width selection.
9+
10+See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim) and Helix's [Migrating from Vim](https://github.com/helix-editor/helix/wiki/Migrating-from-Vim).
11+
12+> TODO: Mention textobjects, surround, registers
book/src/generated/lang-support.mdnew file+193
@@ -0,0 +1,193 @@
1+| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
2+| --- | --- | --- | --- | --- |
3+| agda | ✓ | | | |
4+| astro | ✓ | | | |
5+| awk | ✓ | ✓ | | `awk-language-server` |
6+| bash | ✓ | ✓ | ✓ | `bash-language-server` |
7+| bass | ✓ | | | `bass` |
8+| beancount | ✓ | | | |
9+| bibtex | ✓ | | | `texlab` |
10+| bicep | ✓ | | | `bicep-langserver` |
11+| blueprint | ✓ | | | `blueprint-compiler` |
12+| c | ✓ | ✓ | ✓ | `clangd` |
13+| c-sharp | ✓ | ✓ | | `OmniSharp` |
14+| cabal | | | | `haskell-language-server-wrapper` |
15+| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
16+| capnp | ✓ | | ✓ | |
17+| clojure | ✓ | | | `clojure-lsp` |
18+| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
19+| comment | ✓ | | | |
20+| common-lisp | ✓ | | ✓ | `cl-lsp` |
21+| cpon | ✓ | | ✓ | |
22+| cpp | ✓ | ✓ | ✓ | `clangd` |
23+| crystal | ✓ | ✓ | | `crystalline` |
24+| css | ✓ | | | `vscode-css-language-server` |
25+| cue | ✓ | | | `cuelsp` |
26+| d | ✓ | ✓ | ✓ | `serve-d` |
27+| dart | ✓ | | ✓ | `dart` |
28+| dbml | ✓ | | | |
29+| devicetree | ✓ | | | |
30+| dhall | ✓ | ✓ | | `dhall-lsp-server` |
31+| diff | ✓ | | | |
32+| dockerfile | ✓ | | | `docker-langserver` |
33+| dot | ✓ | | | `dot-language-server` |
34+| dtd | ✓ | | | |
35+| edoc | ✓ | | | |
36+| eex | ✓ | | | |
37+| ejs | ✓ | | | |
38+| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
39+| elm | ✓ | ✓ | | `elm-language-server` |
40+| elvish | ✓ | | | `elvish` |
41+| env | ✓ | | | |
42+| erb | ✓ | | | |
43+| erlang | ✓ | ✓ | | `erlang_ls` |
44+| esdl | ✓ | | | |
45+| fish | ✓ | ✓ | ✓ | |
46+| forth | ✓ | | | `forth-lsp` |
47+| fortran | ✓ | | ✓ | `fortls` |
48+| fsharp | ✓ | | | `fsautocomplete` |
49+| gas | ✓ | ✓ | | |
50+| gdscript | ✓ | ✓ | ✓ | |
51+| gemini | ✓ | | | |
52+| git-attributes | ✓ | | | |
53+| git-commit | ✓ | ✓ | | |
54+| git-config | ✓ | | | |
55+| git-ignore | ✓ | | | |
56+| git-rebase | ✓ | | | |
57+| gleam | ✓ | ✓ | | `gleam` |
58+| glsl | ✓ | ✓ | ✓ | |
59+| gn | ✓ | | | |
60+| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
61+| godot-resource | ✓ | | | |
62+| gomod | ✓ | | | `gopls` |
63+| gotmpl | ✓ | | | `gopls` |
64+| gowork | ✓ | | | `gopls` |
65+| graphql | ✓ | | | `graphql-lsp` |
66+| hare | ✓ | | | |
67+| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68+| haskell-persistent | ✓ | | | |
69+| hcl | ✓ | | ✓ | `terraform-ls` |
70+| heex | ✓ | ✓ | | `elixir-ls` |
71+| hosts | ✓ | | | |
72+| html | ✓ | | | `vscode-html-language-server` |
73+| hurl | ✓ | | ✓ | |
74+| idris | | | | `idris2-lsp` |
75+| iex | ✓ | | | |
76+| ini | ✓ | | | |
77+| janet | ✓ | | | |
78+| java | ✓ | ✓ | ✓ | `jdtls` |
79+| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80+| jinja | ✓ | | | |
81+| jsdoc | ✓ | | | |
82+| json | ✓ | | ✓ | `vscode-json-language-server` |
83+| json5 | ✓ | | | |
84+| jsonnet | ✓ | | | `jsonnet-language-server` |
85+| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
86+| julia | ✓ | ✓ | ✓ | `julia` |
87+| just | ✓ | ✓ | ✓ | |
88+| kdl | ✓ | ✓ | ✓ | |
89+| kotlin | ✓ | | | `kotlin-language-server` |
90+| latex | ✓ | ✓ | | `texlab` |
91+| lean | ✓ | | | `lean` |
92+| ledger | ✓ | | | |
93+| llvm | ✓ | ✓ | ✓ | |
94+| llvm-mir | ✓ | ✓ | ✓ | |
95+| llvm-mir-yaml | ✓ | | ✓ | |
96+| log | ✓ | | | |
97+| lpf | ✓ | | | |
98+| lua | ✓ | ✓ | ✓ | `lua-language-server` |
99+| make | ✓ | | | |
100+| markdoc | ✓ | | | `markdoc-ls` |
101+| markdown | ✓ | | | `marksman` |
102+| markdown.inline | ✓ | | | |
103+| matlab | ✓ | ✓ | ✓ | |
104+| mermaid | ✓ | | | |
105+| meson | ✓ | | ✓ | |
106+| mint | | | | `mint` |
107+| msbuild | ✓ | | ✓ | |
108+| nasm | ✓ | ✓ | | |
109+| nickel | ✓ | | ✓ | `nls` |
110+| nim | ✓ | ✓ | ✓ | `nimlangserver` |
111+| nix | ✓ | | | `nil` |
112+| nu | ✓ | | | `nu` |
113+| nunjucks | ✓ | | | |
114+| ocaml | ✓ | | ✓ | `ocamllsp` |
115+| ocaml-interface | ✓ | | | `ocamllsp` |
116+| odin | ✓ | | ✓ | `ols` |
117+| opencl | ✓ | ✓ | ✓ | `clangd` |
118+| openscad | ✓ | | | `openscad-lsp` |
119+| org | ✓ | | | |
120+| pascal | ✓ | ✓ | | `pasls` |
121+| passwd | ✓ | | | |
122+| pem | ✓ | | | |
123+| perl | ✓ | ✓ | ✓ | `perlnavigator` |
124+| php | ✓ | ✓ | ✓ | `intelephense` |
125+| po | ✓ | ✓ | | |
126+| pod | ✓ | | | |
127+| ponylang | ✓ | ✓ | ✓ | |
128+| prisma | ✓ | | | `prisma-language-server` |
129+| prolog | | | | `swipl` |
130+| protobuf | ✓ | | ✓ | `bufls`, `pb` |
131+| prql | ✓ | | | |
132+| purescript | ✓ | ✓ | | `purescript-language-server` |
133+| python | ✓ | ✓ | ✓ | `pylsp` |
134+| qml | ✓ | | ✓ | `qmlls` |
135+| r | ✓ | | | `R` |
136+| racket | ✓ | | ✓ | `racket` |
137+| regex | ✓ | | | |
138+| rego | ✓ | | | `regols` |
139+| rescript | ✓ | ✓ | | `rescript-language-server` |
140+| rmarkdown | ✓ | | ✓ | `R` |
141+| robot | ✓ | | | `robotframework_ls` |
142+| ron | ✓ | | ✓ | |
143+| rst | ✓ | | | |
144+| ruby | ✓ | ✓ | ✓ | `solargraph` |
145+| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
146+| sage | ✓ | ✓ | | |
147+| scala | ✓ | | ✓ | `metals` |
148+| scheme | ✓ | | ✓ | |
149+| scss | ✓ | | | `vscode-css-language-server` |
150+| slint | ✓ | | ✓ | `slint-lsp` |
151+| smali | ✓ | | ✓ | |
152+| smithy | ✓ | | | `cs` |
153+| sml | ✓ | | | |
154+| solidity | ✓ | | | `solc` |
155+| sql | ✓ | | | |
156+| sshclientconfig | ✓ | | | |
157+| starlark | ✓ | ✓ | | |
158+| strace | ✓ | | | |
159+| svelte | ✓ | | ✓ | `svelteserver` |
160+| sway | ✓ | ✓ | ✓ | `forc` |
161+| swift | ✓ | | | `sourcekit-lsp` |
162+| t32 | ✓ | | | |
163+| tablegen | ✓ | ✓ | ✓ | |
164+| task | ✓ | | | |
165+| templ | ✓ | | | `templ` |
166+| tfvars | ✓ | | ✓ | `terraform-ls` |
167+| todotxt | ✓ | | | |
168+| toml | ✓ | | | `taplo` |
169+| tsq | ✓ | | | |
170+| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
171+| twig | ✓ | | | |
172+| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173+| typst | ✓ | | | `typst-lsp` |
174+| ungrammar | ✓ | | | |
175+| unison | ✓ | | | |
176+| uxntal | ✓ | | | |
177+| v | ✓ | ✓ | ✓ | `v-analyzer` |
178+| vala | ✓ | | | `vala-language-server` |
179+| verilog | ✓ | ✓ | | `svlangserver` |
180+| vhdl | ✓ | | | `vhdl_ls` |
181+| vhs | ✓ | | | |
182+| vue | ✓ | | | `vue-language-server` |
183+| wast | ✓ | | | |
184+| wat | ✓ | | | |
185+| webc | ✓ | | | |
186+| wgsl | ✓ | | | `wgsl_analyzer` |
187+| wit | ✓ | | ✓ | |
188+| wren | ✓ | ✓ | ✓ | |
189+| xit | ✓ | | | |
190+| xml | ✓ | | ✓ | |
191+| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
192+| yuck | ✓ | | | |
193+| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.mdnew file+88
@@ -0,0 +1,88 @@
1+| Name | Description |
2+| --- | --- |
3+| `:quit`, `:q` | Close the current view. |
4+| `:quit!`, `:q!` | Force close the current view, ignoring unsaved changes. |
5+| `:open`, `:o` | Open a file from disk into the current view. |
6+| `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. |
7+| `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully, ignoring unsaved changes. |
8+| `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. |
9+| `:buffer-close-others!`, `:bco!`, `:bcloseother!` | Force close all buffers but the currently focused one. |
10+| `:buffer-close-all`, `:bca`, `:bcloseall` | Close all buffers without quitting. |
11+| `:buffer-close-all!`, `:bca!`, `:bcloseall!` | Force close all buffers ignoring unsaved changes without quitting. |
12+| `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
13+| `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
14+| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
15+| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt) |
16+| `:write-buffer-close`, `:wbc` | Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt) |
17+| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
18+| `:new`, `:n` | Create a new scratch buffer. |
19+| `:format`, `:fmt` | Format the file using the LSP formatter. |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
21+| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
22+| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
23+| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
24+| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
25+| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
26+| `:write-all`, `:wa` | Write changes from all buffers to disk. |
27+| `:write-all!`, `:wa!` | Forcefully write changes from all buffers to disk creating necessary subdirectories. |
28+| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
29+| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
30+| `:quit-all`, `:qa` | Close all views. |
31+| `:quit-all!`, `:qa!` | Force close all views ignoring unsaved changes. |
32+| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
33+| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
34+| `:theme` | Change the editor theme (show current theme if no name specified). |
35+| `:yank-join` | Yank joined selections. A separator can be provided as first argument. Default value is newline. |
36+| `:clipboard-yank` | Yank main selection into system clipboard. |
37+| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
38+| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
39+| `:primary-clipboard-yank-join` | Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline. |
40+| `:clipboard-paste-after` | Paste system clipboard after selections. |
41+| `:clipboard-paste-before` | Paste system clipboard before selections. |
42+| `:clipboard-paste-replace` | Replace selections with content of system clipboard. |
43+| `:primary-clipboard-paste-after` | Paste primary clipboard after selections. |
44+| `:primary-clipboard-paste-before` | Paste primary clipboard before selections. |
45+| `:primary-clipboard-paste-replace` | Replace selections with content of system primary clipboard. |
46+| `:show-clipboard-provider` | Show clipboard provider name in status bar. |
47+| `:change-current-directory`, `:cd` | Change the current working directory. |
48+| `:show-directory`, `:pwd` | Show the current working directory. |
49+| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
50+| `:character-info`, `:char` | Get info about the character under the primary cursor. |
51+| `:reload`, `:rl` | Discard changes and reload from the source file. |
52+| `:reload-all`, `:rla` | Discard changes and reload all documents from the source files. |
53+| `:update`, `:u` | Write changes only if the file has been modified. |
54+| `:lsp-workspace-command` | Open workspace command picker |
55+| `:lsp-restart` | Restarts the language servers used by the current doc |
56+| `:lsp-stop` | Stops the language servers that are used by the current doc |
57+| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
58+| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
59+| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
60+| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
61+| `:debug-eval` | Evaluate expression in current debug context. |
62+| `:vsplit`, `:vs` | Open the file in a vertical split. |
63+| `:vsplit-new`, `:vnew` | Open a scratch buffer in a vertical split. |
64+| `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. |
65+| `:hsplit-new`, `:hnew` | Open a scratch buffer in a horizontal split. |
66+| `:tutor` | Open the tutorial. |
67+| `:goto`, `:g` | Goto line number. |
68+| `:set-language`, `:lang` | Set the language of current buffer (show current language if no value specified). |
69+| `:set-option`, `:set` | Set a config option at runtime.<br>For example to disable smart case search, use `:set search.smart-case false`. |
70+| `:toggle-option`, `:toggle` | Toggle a boolean config option at runtime.<br>For example to toggle smart case search, use `:toggle search.smart-case`. |
71+| `:get-option`, `:get` | Get the current value of a config option. |
72+| `:sort` | Sort ranges in selection. |
73+| `:rsort` | Sort ranges in selection in reverse order. |
74+| `:reflow` | Hard-wrap the current selection of lines to a given width. |
75+| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
76+| `:config-reload` | Refresh user config. |
77+| `:config-open` | Open the user config.toml file. |
78+| `:config-open-workspace` | Open the workspace config.toml file. |
79+| `:log-open` | Open the helix log file. |
80+| `:insert-output` | Run shell command, inserting output before each selection. |
81+| `:append-output` | Run shell command, appending output after each selection. |
82+| `:pipe` | Pipe each selection to the shell command. |
83+| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
84+| `:run-shell-command`, `:sh` | Run a shell command |
85+| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
86+| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
87+| `:redraw` | Clear and re-render the whole UI |
88+| `:move` | Move the current buffer and its corresponding file to a different path |
book/src/guides/README.mdnew file+4
@@ -0,0 +1,4 @@
1+# Guides
2+
3+This section contains guides for adding new language server configurations,
4+tree-sitter grammars, textobject queries, and other similar items.
book/src/guides/adding_languages.mdnew file+55
@@ -0,0 +1,55 @@
1+# Adding new languages to Helix
2+
3+In order to add a new language to Helix, you will need to follow the steps
4+below.
5+
6+## Language configuration
7+
8+1. Add a new `[[language]]` entry in the `languages.toml` file and provide the
9+ necessary configuration for the new language. For more information on
10+ language configuration, refer to the
11+ [language configuration section](../languages.md) of the documentation.
12+ A new language server can be added by extending the `[language-server]` table in the same file.
13+2. If you are adding a new language or updating an existing language server
14+ configuration, run the command `cargo xtask docgen` to update the
15+ [Language Support](../lang-support.md) documentation.
16+
17+> 💡 If you are adding a new Language Server configuration, make sure to update
18+> the
19+> [Language Server Wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
20+> with the installation instructions.
21+
22+## Grammar configuration
23+
24+1. If a tree-sitter grammar is available for the new language, add a new
25+ `[[grammar]]` entry to the `languages.toml` file.
26+2. If you are testing the grammar locally, you can use the `source.path` key
27+ with an absolute path to the grammar. However, before submitting a pull
28+ request, make sure to switch to using `source.git`.
29+
30+## Queries
31+
32+1. In order to provide syntax highlighting and indentation for the new language,
33+ you will need to add queries.
34+2. Create a new directory for the language with the path
35+ `runtime/queries/<name>/`.
36+3. Refer to the
37+ [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
38+ for more information on writing queries.
39+4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
40+
41+> 💡 In Helix, the first matching query takes precedence when evaluating
42+> queries, which is different from other editors such as Neovim where the last
43+> matching query supersedes the ones before it. See
44+> [this issue](https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090)
45+> for an example.
46+
47+## Common issues
48+
49+- If you encounter errors when running Helix after switching branches, you may
50+ need to update the tree-sitter grammars. Run the command `hx --grammar fetch`
51+ to fetch the grammars and `hx --grammar build` to build any out-of-date
52+ grammars.
53+- If a parser is causing a segfault, or you want to remove it, make sure to
54+ remove the compiled parser located at `runtime/grammars/<name>.so`.
55+- If you are attempting to add queries and Helix is unable to locate them, ensure that the environment variable `HELIX_RUNTIME` is set to the location of the `runtime` folder you're developing in.
book/src/guides/indent.mdnew file+369
@@ -0,0 +1,369 @@
1+# Adding indent queries
2+
3+Helix uses tree-sitter to correctly indent new lines. This requires a tree-
4+sitter grammar and an `indent.scm` query file placed in `runtime/queries/
5+{language}/indents.scm`. The indentation for a line is calculated by traversing
6+the syntax tree from the lowest node at the beginning of the new line (see
7+[Indent queries](#indent-queries)). Each of these nodes contributes to the total
8+indent when it is captured by the query (in what way depends on the name of
9+the capture.
10+
11+Note that it matters where these added indents begin. For example,
12+multiple indent level increases that start on the same line only increase
13+the total indent level by 1. See [Capture types](#capture-types).
14+
15+By default, Helix uses the `hybrid` indentation heuristic. This means that
16+indent queries are not used to compute the expected absolute indentation of a
17+line but rather the expected difference in indentation between the new and an
18+already existing line. This difference is then added to the actual indentation
19+of the already existing line. Since this makes errors in the indent queries
20+harder to find, it is recommended to disable it when testing via
21+`:set indent-heuristic tree-sitter`. The rest of this guide assumes that
22+the `tree-sitter` heuristic is used.
23+
24+## Indent queries
25+
26+When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
27+the indent level for the new line, the query in `indents.scm` is run on the
28+document. The starting position of the query is the end of the line above where
29+a new line will be inserted.
30+
31+For `o`, the inserted line is the line below the cursor, so that starting
32+position of the query is the end of the current line.
33+
34+```rust
35+fn need_hero(some_hero: Hero, life: Life) -> {
36+ matches!(some_hero, Hero { // ←─────────────────╮
37+ strong: true,//←╮ ↑ ↑ │
38+ fast: true, // │ │ ╰── query start │
39+ sure: true, // │ ╰───── cursor ├─ traversal
40+ soon: true, // ╰──────── new line inserted │ start node
41+ }) && // │
42+// ↑ │
43+// ╰───────────────────────────────────────────────╯
44+ some_hero > life
45+}
46+```
47+
48+For `O`, the newly inserted line is the *current* line, so the starting position
49+of the query is the end of the line above the cursor.
50+
51+```rust
52+fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
53+ matches!(some_hero, Hero { // ←╮ ↑ │
54+ strong: true,// ↑ ╭───╯ │ │
55+ fast: true, // │ │ query start ─╯ │
56+ sure: true, // ╰───┼ cursor ├─ traversal
57+ soon: true, // ╰ new line inserted │ start node
58+ }) && // │
59+ some_hero > life // │
60+} // ←──────────────────────────────────────────────╯
61+```
62+
63+From this starting node, the syntax tree is traversed up until the root node.
64+Each indent capture is collected along the way, and then combined according to
65+their [capture types](#capture-types) and [scopes](#scopes) to a final indent
66+level for the line.
67+
68+### Capture types
69+
70+- `@indent` (default scope `tail`):
71+ Increase the indent level by 1. Multiple occurrences in the same line *do not*
72+ stack. If there is at least one `@indent` and one `@outdent` capture on the
73+ same line, the indent level isn't changed at all.
74+- `@outdent` (default scope `all`):
75+ Decrease the indent level by 1. The same rules as for `@indent` apply.
76+- `@indent.always` (default scope `tail`):
77+ Increase the indent level by 1. Multiple occurrences on the same line *do*
78+ stack. The final indent level is `@indent.always` – `@outdent.always`. If
79+ an `@indent` and an `@indent.always` are on the same line, the `@indent` is
80+ ignored.
81+- `@outdent.always` (default scope `all`):
82+ Decrease the indent level by 1. The same rules as for `@indent.always` apply.
83+- `@align` (default scope `all`):
84+ Align everything inside this node to some anchor. The anchor is given
85+ by the start of the node captured by `@anchor` in the same pattern.
86+ Every pattern with an `@align` should contain exactly one `@anchor`.
87+ Indent (and outdent) for nodes below (in terms of their starting line)
88+ the `@align` node is added to the indentation required for alignment.
89+- `@extend`:
90+ Extend the range of this node to the end of the line and to lines that are
91+ indented more than the line that this node starts on. This is useful for
92+ languages like Python, where for the purpose of indentation some nodes (like
93+ functions or classes) should also contain indented lines that follow them.
94+- `@extend.prevent-once`:
95+ Prevents the first extension of an ancestor of this node. For example, in Python
96+ a return expression always ends the block that it is in. Note that this only
97+ stops the extension of the next `@extend` capture. If multiple ancestors are
98+ captured, only the extension of the innermost one is prevented. All other
99+ ancestors are unaffected (regardless of whether the innermost ancestor would
100+ actually have been extended).
101+
102+#### `@indent` / `@outdent`
103+
104+Consider this example:
105+
106+```rust
107+fn shout(things: Vec<Thing>) {
108+ // ↑
109+ // ├───────────────────────╮ indent level
110+ // @indent ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
111+ // │
112+ let it_all = |out| { things.filter(|thing| { // │ 1
113+ // ↑ ↑ │
114+ // ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
115+ // @indent @indent │
116+ // │ 2
117+ thing.can_do_with(out) // │
118+ })}; // ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
119+ //↑↑↑ │ 1
120+} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
121+// 3x @outdent
122+```
123+
124+```scm
125+((block) @indent)
126+["}" ")"] @outdent
127+```
128+
129+Note how on the second line, we have two blocks begin on the same line. In this
130+case, since both captures occur on the same line, they are combined and only
131+result in a net increase of 1. Also note that the closing `}`s are part of the
132+`@indent` captures, but the 3 `@outdent`s also combine into 1 and result in that
133+line losing one indent level.
134+
135+#### `@extend` / `@extend.prevent-once`
136+
137+For an example of where `@extend` can be useful, consider Python, which is
138+whitespace-sensitive.
139+
140+```scm
141+]
142+ (parenthesized_expression)
143+ (function_definition)
144+ (class_definition)
145+] @indent
146+
147+```
148+
149+```python
150+class Hero:
151+ def __init__(self, strong, fast, sure, soon):# ←─╮
152+ self.is_strong = strong # │
153+ self.is_fast = fast # ╭─── query start │
154+ self.is_sure = sure # │ ╭─ cursor │
155+ self.is_soon = soon # │ │ │
156+ # ↑ ↑ │ │ │
157+ # │ ╰──────╯ │ │
158+ # ╰─────────────────────╯ │
159+ # ├─ traversal
160+ def need_hero(self, life): # │ start node
161+ return ( # │
162+ self.is_strong # │
163+ and self.is_fast # │
164+ and self.is_sure # │
165+ and self.is_soon # │
166+ and self > life # │
167+ ) # ←─────────────────────────────────────────╯
168+```
169+
170+Without braces to catch the scope of the function, the smallest descendant of
171+the cursor on a line feed ends up being the entire inside of the class. Because
172+of this, it will miss the entire function node and its indent capture, leading
173+to an indent level one too small.
174+
175+To address this case, `@extend` tells helix to "extend" the captured node's span
176+to the line feed and every consecutive line that has a greater indent level than
177+the line of the node.
178+
179+```scm
180+(parenthesized_expression) @indent
181+
182+]
183+ (function_definition)
184+ (class_definition)
185+] @indent @extend
186+
187+```
188+
189+```python
190+class Hero:
191+ def __init__(self, strong, fast, sure, soon):# ←─╮
192+ self.is_strong = strong # │
193+ self.is_fast = fast # ╭─── query start ├─ traversal
194+ self.is_sure = sure # │ ╭─ cursor │ start node
195+ self.is_soon = soon # │ │ ←───────────────╯
196+ # ↑ ↑ │ │
197+ # │ ╰──────╯ │
198+ # ╰─────────────────────╯
199+ def need_hero(self, life):
200+ return (
201+ self.is_strong
202+ and self.is_fast
203+ and self.is_sure
204+ and self.is_soon
205+ and self > life
206+ )
207+```
208+
209+Furthermore, there are some cases where extending to everything with a greater
210+indent level may not be desirable. Consider the `need_hero` function above. If
211+our cursor is on the last line of the returned expression.
212+
213+```python
214+class Hero:
215+ def __init__(self, strong, fast, sure, soon):
216+ self.is_strong = strong
217+ self.is_fast = fast
218+ self.is_sure = sure
219+ self.is_soon = soon
220+
221+ def need_hero(self, life):
222+ return (
223+ self.is_strong
224+ and self.is_fast
225+ and self.is_sure
226+ and self.is_soon
227+ and self > life
228+ ) # ←─── cursor
229+ #←────────── where cursor should go on new line
230+```
231+
232+In Python, the are a few tokens that will always end a scope, such as a return
233+statement. Since the scope ends, so should the indent level. But because the
234+function span is extended to every line with a greater indent level, a new line
235+would just continue on the same level. And an `@outdent` would not help us here
236+either, since it would cause everything in the parentheses to become outdented
237+as well.
238+
239+To help, we need to signal an end to the extension. We can do this with
240+`@extend.prevent-once`.
241+
242+```scm
243+(parenthesized_expression) @indent
244+
245+]
246+ (function_definition)
247+ (class_definition)
248+] @indent @extend
249+
250+(return_statement) @extend.prevent-once
251+```
252+
253+#### `@indent.always` / `@outdent.always`
254+
255+As mentioned before, normally if there is more than one `@indent` or `@outdent`
256+capture on the same line, they are combined.
257+
258+Sometimes, there are cases when you may want to ensure that every indent capture
259+is additive, regardless of how many occur on the same line. Consider this
260+example in YAML.
261+
262+```yaml
263+ - foo: bar
264+# ↑ ↑
265+# │ ╰─────────────── start of map
266+# ╰───────────────── start of list element
267+ baz: quux # ←─── cursor
268+ # ←───────────── where the cursor should go on a new line
269+ garply: waldo
270+ - quux:
271+ bar: baz
272+ xyzzy: thud
273+ fred: plugh
274+```
275+
276+In YAML, you often have lists of maps. In these cases, the syntax is such that
277+the list element and the map both start on the same line. But we really do want
278+to start an indentation for each of these so that subsequent keys in the map
279+hang over the list and align properly. This is where `@indent.always` helps.
280+
281+```scm
282+((block_sequence_item) @item @indent.always @extend
283+ (#not-one-line? @item))
284+
285+((block_mapping_pair
286+ key: (_) @key
287+ value: (_) @val
288+ (#not-same-line? @key @val)
289+ ) @indent.always @extend
290+)
291+```
292+
293+## Predicates
294+
295+In some cases, an S-expression cannot express exactly what pattern should be matched.
296+For that, tree-sitter allows for predicates to appear anywhere within a pattern,
297+similar to how `#set!` declarations work:
298+
299+```scm
300+(some_kind
301+ (child_kind) @indent
302+ (#predicate? arg1 arg2 ...)
303+)
304+```
305+
306+The number of arguments depends on the predicate that's used.
307+Each argument is either a capture (`@name`) or a string (`"some string"`).
308+The following predicates are supported by tree-sitter:
309+
310+- `#eq?`/`#not-eq?`:
311+The first argument (a capture) must/must not be equal to the second argument
312+(a capture or a string).
313+
314+- `#match?`/`#not-match?`:
315+The first argument (a capture) must/must not match the regex given in the
316+second argument (a string).
317+
318+Additionally, we support some custom predicates for indent queries:
319+
320+- `#not-kind-eq?`:
321+The kind of the first argument (a capture) must not be equal to the second
322+argument (a string).
323+
324+- `#same-line?`/`#not-same-line?`:
325+The captures given by the 2 arguments must/must not start on the same line.
326+
327+- `#one-line?`/`#not-one-line?`:
328+The captures given by the fist argument must/must span a total of one line.
329+
330+### Scopes
331+
332+Added indents don't always apply to the whole node. For example, in most
333+cases when a node should be indented, we actually only want everything
334+except for its first line to be indented. For this, there are several
335+scopes (more scopes may be added in the future if required):
336+
337+- `tail`:
338+This scope applies to everything except for the first line of the
339+captured node.
340+- `all`:
341+This scope applies to the whole captured node. This is only different from
342+`tail` when the captured node is the first node on its line.
343+
344+For example, imagine we have the following function
345+
346+```rust
347+fn aha() { // ←─────────────────────────────────────╮
348+ let take = "on me"; // ←──────────────╮ scope: │
349+ let take = "me on"; // ├─ "tail" ├─ (block) @indent
350+ let ill = be_gone_days(1 || 2); // │ │
351+} // ←───────────────────────────────────┴──────────┴─ "}" @outdent
352+ // scope: "all"
353+```
354+
355+We can write the following query with the `#set!` declaration:
356+
357+ ```scm
358+ ((block) @indent
359+ (#set! "scope" "tail"))
360+ ("}" @outdent
361+ (#set! "scope" "all"))
362+ ```
363+
364+As we can see, the "tail" scope covers the node, except for the first line.
365+Everything up to and including the closing brace gets an indent level of 1.
366+Then, on the closing brace, we encounter an outdent with a scope of "all", which
367+means the first line is included, and the indent level is cancelled out on this
368+line. (Note these scopes are the defaults for `@indent` and `@outdent`—they are
369+written explicitly for demonstration.)
book/src/guides/injection.mdnew file+57
@@ -0,0 +1,57 @@
1+# Adding Injection Queries
2+
3+Writing language injection queries allows one to highlight a specific node as a different language.
4+In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
5+are a few Helix specific extensions that allow for more control.
6+
7+And example of a simple query that would highlight all strings as bash in Nix:
8+```scm
9+((string_expression (string_fragment) @injection.content)
10+ (#set! injection.language "bash"))
11+```
12+
13+## Capture Types
14+
15+- `@injection.language` (standard):
16+The captured node may contain the language name used to highlight the node captured by
17+`@injection.content`.
18+
19+- `@injection.content` (standard):
20+Marks the content to be highlighted as the language captured with `@injection.language` _et al_.
21+
22+- `@injection.filename` (extension):
23+The captured node may contain a filename with a file-extension known to Helix,
24+highlighting `@injection.content` as that language. This uses the language extensions defined in
25+both the default languages.toml distributed with Helix, as well as user defined languages.
26+
27+- `@injection.shebang` (extension):
28+The captured node may contain a shebang used to choose a language to highlight as. This also uses
29+the shebangs defined in the default and user `languages.toml`.
30+
31+## Settings
32+
33+- `injection.combined` (standard):
34+Indicates that all the matching nodes in the tree should have their content parsed as one
35+nested document.
36+
37+- `injection.language` (standard):
38+Forces the captured content to be highlighted as the given language
39+
40+- `injection.include-children` (standard):
41+Indicates that the content node’s entire text should be re-parsed, including the text of its child
42+nodes. By default, child nodes’ text will be excluded from the injected document.
43+
44+- `injection.include-unnamed-children` (extension):
45+Same as `injection.include-children` but only for unnamed child nodes.
46+
47+## Predicates
48+
49+- `#eq?` (standard):
50+The first argument (a capture) must be equal to the second argument
51+(a capture or a string).
52+
53+- `#match?` (standard):
54+The first argument (a capture) must match the regex given in the
55+second argument (a string).
56+
57+[upstream-docs]: http://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
book/src/guides/textobject.mdnew file+47
@@ -0,0 +1,47 @@
1+# Adding textobject queries
2+
3+Helix supports textobjects that are language specific, such as functions, classes, etc.
4+These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
5+to work properly. Tree-sitter allows us to query the source code syntax tree
6+and capture specific parts of it. The queries are written in a lisp dialect.
7+More information on how to write queries can be found in the [official tree-sitter
8+documentation][tree-sitter-queries].
9+
10+Query files should be placed in `runtime/queries/{language}/textobjects.scm`
11+when contributing to Helix. Note that to test the query files locally you should put
12+them under your local runtime directory (`~/.config/helix/runtime` on Linux
13+for example).
14+
15+The following [captures][tree-sitter-captures] are recognized:
16+
17+| Capture Name |
18+| --- |
19+| `function.inside` |
20+| `function.around` |
21+| `class.inside` |
22+| `class.around` |
23+| `test.inside` |
24+| `test.around` |
25+| `parameter.inside` |
26+| `comment.inside` |
27+| `comment.around` |
28+
29+[Example query files][textobject-examples] can be found in the helix GitHub repository.
30+
31+## Queries for textobject based navigation
32+
33+Tree-sitter based navigation in Helix is done using captures in the
34+following order:
35+
36+- `object.movement`
37+- `object.around`
38+- `object.inside`
39+
40+For example if a `function.around` capture has been already defined for a language
41+in its `textobjects.scm` file, function navigation should also work automatically.
42+`function.movement` should be defined only if the node captured by `function.around`
43+doesn't make sense in a navigation context.
44+
45+[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
46+[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
47+[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
book/src/install.mdnew file+320
@@ -0,0 +1,320 @@
1+# Installing Helix
2+
3+<!--toc:start-->
4+- [Pre-built binaries](#pre-built-binaries)
5+- [Linux, macOS, Windows and OpenBSD packaging status](#linux-macos-windows-and-openbsd-packaging-status)
6+- [Linux](#linux)
7+ - [Ubuntu](#ubuntu)
8+ - [Fedora/RHEL](#fedorarhel)
9+ - [Arch Linux extra](#arch-linux-extra)
10+ - [NixOS](#nixos)
11+ - [Flatpak](#flatpak)
12+ - [Snap](#snap)
13+ - [AppImage](#appimage)
14+- [macOS](#macos)
15+ - [Homebrew Core](#homebrew-core)
16+ - [MacPorts](#macports)
17+- [Windows](#windows)
18+ - [Winget](#winget)
19+ - [Scoop](#scoop)
20+ - [Chocolatey](#chocolatey)
21+ - [MSYS2](#msys2)
22+- [Building from source](#building-from-source)
23+ - [Configuring Helix's runtime files](#configuring-helixs-runtime-files)
24+ - [Linux and macOS](#linux-and-macos)
25+ - [Windows](#windows)
26+ - [Multiple runtime directories](#multiple-runtime-directories)
27+ - [Validating the installation](#validating-the-installation)
28+ - [Configure the desktop shortcut](#configure-the-desktop-shortcut)
29+<!--toc:end-->
30+
31+To install Helix, follow the instructions specific to your operating system.
32+Note that:
33+
34+- To get the latest nightly version of Helix, you need to
35+ [build from source](#building-from-source).
36+
37+- To take full advantage of Helix, install the language servers for your
38+ preferred programming languages. See the
39+ [wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
40+ for instructions.
41+
42+## Pre-built binaries
43+
44+Download pre-built binaries from the
45+[GitHub Releases page](https://github.com/helix-editor/helix/releases). Add the binary to your system's `$PATH` to use it from the command
46+line.
47+
48+## Linux, macOS, Windows and OpenBSD packaging status
49+
50+[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
51+
52+## Linux
53+
54+The following third party repositories are available:
55+
56+### Ubuntu
57+
58+Add the `PPA` for Helix:
59+
60+```sh
61+sudo add-apt-repository ppa:maveonair/helix-editor
62+sudo apt update
63+sudo apt install helix
64+```
65+
66+### Fedora/RHEL
67+
68+```sh
69+sudo dnf install helix
70+```
71+
72+### Arch Linux extra
73+
74+Releases are available in the `extra` repository:
75+
76+```sh
77+sudo pacman -S helix
78+```
79+Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available
80+in the AUR, which builds the master branch.
81+
82+### NixOS
83+
84+Helix is available in [nixpkgs](https://github.com/nixos/nixpkgs) through the `helix` attribute,
85+the unstable channel usually carries the latest release.
86+
87+Helix is also available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
88+root. Use `nix develop` to spin up a reproducible development shell. Outputs are
89+cached for each push to master using [Cachix](https://www.cachix.org/). The
90+flake is configured to automatically make use of this cache assuming the user
91+accepts the new settings on first use.
92+
93+If you are using a version of Nix without flakes enabled,
94+[install Cachix CLI](https://docs.cachix.org/installation) and use
95+`cachix use helix` to configure Nix to use cached outputs when possible.
96+
97+### Flatpak
98+
99+Helix is available on [Flathub](https://flathub.org/en-GB/apps/com.helix_editor.Helix):
100+
101+```sh
102+flatpak install flathub com.helix_editor.Helix
103+flatpak run com.helix_editor.Helix
104+```
105+
106+### Snap
107+
108+Helix is available on [Snapcraft](https://snapcraft.io/helix) and can be installed with:
109+
110+```sh
111+snap install --classic helix
112+```
113+
114+This will install Helix as both `/snap/bin/helix` and `/snap/bin/hx`, so make sure `/snap/bin` is in your `PATH`.
115+
116+### AppImage
117+
118+Install Helix using the Linux [AppImage](https://appimage.org/) format.
119+Download the official Helix AppImage from the [latest releases](https://github.com/helix-editor/helix/releases/latest) page.
120+
121+```sh
122+chmod +x helix-*.AppImage # change permission for executable mode
123+./helix-*.AppImage # run helix
124+```
125+
126+## macOS
127+
128+### Homebrew Core
129+
130+```sh
131+brew install helix
132+```
133+
134+### MacPorts
135+
136+```sh
137+port install helix
138+```
139+
140+## Windows
141+
142+Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
143+or [MSYS2](https://msys2.org/).
144+
145+### Winget
146+Windows Package Manager winget command-line tool is by default available on Windows 11 and modern versions of Windows 10 as a part of the App Installer.
147+You can get [App Installer from the Microsoft Store](https://www.microsoft.com/p/app-installer/9nblggh4nns1#activetab=pivot:overviewtab). If it's already installed, make sure it is updated with the latest version.
148+
149+```sh
150+winget install Helix.Helix
151+```
152+
153+### Scoop
154+
155+```sh
156+scoop install helix
157+```
158+
159+### Chocolatey
160+
161+```sh
162+choco install helix
163+```
164+
165+### MSYS2
166+
167+For 64-bit Windows 8.1 or above:
168+
169+```sh
170+pacman -S mingw-w64-ucrt-x86_64-helix
171+```
172+
173+## Building from source
174+
175+Requirements:
176+
177+Clone the Helix GitHub repository into a directory of your choice. The
178+examples in this documentation assume installation into either `~/src/` on
179+Linux and macOS, or `%userprofile%\src\` on Windows.
180+
181+- The [Rust toolchain](https://www.rust-lang.org/tools/install)
182+- The [Git version control system](https://git-scm.com/)
183+- A C++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
184+
185+If you are using the `musl-libc` standard library instead of `glibc` the following environment variable must be set during the build to ensure tree-sitter grammars can be loaded correctly:
186+
187+```sh
188+RUSTFLAGS="-C target-feature=-crt-static"
189+```
190+
191+1. Clone the repository:
192+
193+ ```sh
194+ git clone https://github.com/helix-editor/helix
195+ cd helix
196+ ```
197+
198+2. Compile from source:
199+
200+ ```sh
201+ cargo install --path helix-term --locked
202+ ```
203+
204+ This command will create the `hx` executable and construct the tree-sitter
205+ grammars in the local `runtime` folder.
206+
207+> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
208+> grammars with `hx --grammar fetch` and compile them with
209+> `hx --grammar build`. This will install them in
210+> the `runtime` directory within the user's helix config directory (more
211+> [details below](#multiple-runtime-directories)).
212+
213+### Configuring Helix's runtime files
214+
215+#### Linux and macOS
216+
217+The **runtime** directory is one below the Helix source, so either set a
218+`HELIX_RUNTIME` environment variable to point to that directory and add it to
219+your `~/.bashrc` or equivalent:
220+
221+```sh
222+HELIX_RUNTIME=~/src/helix/runtime
223+```
224+
225+Or, create a symbolic link:
226+
227+```sh
228+ln -Ts $PWD/runtime ~/.config/helix/runtime
229+```
230+
231+If the above command fails to create a symbolic link because the file exists either move `~/.config/helix/runtime` to a new location or delete it, then run the symlink command above again.
232+
233+#### Windows
234+
235+Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
236+`Edit environment variables for your account`) or use the `setx` command in
237+Cmd:
238+
239+```sh
240+setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime"
241+```
242+
243+> 💡 `%userprofile%` resolves to your user directory like
244+> `C:\Users\Your-Name\` for example.
245+
246+Or, create a symlink in `%appdata%\helix\` that links to the source code directory:
247+
248+| Method | Command |
249+| ---------- | -------------------------------------------------------------------------------------- |
250+| PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` |
251+| Cmd | `cd %appdata%\helix` <br/> `mklink /D runtime "%userprofile%\src\helix\runtime"` |
252+
253+> 💡 On Windows, creating a symbolic link may require running PowerShell or
254+> Cmd as an administrator.
255+
256+#### Multiple runtime directories
257+
258+When Helix finds multiple runtime directories it will search through them for files in the
259+following order:
260+
261+1. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
262+ developing and testing helix only).
263+2. `runtime/` subdirectory of OS-dependent helix user config directory.
264+3. `$HELIX_RUNTIME`
265+4. Distribution-specific fallback directory (set at compile time—not run time—
266+ with the `HELIX_DEFAULT_RUNTIME` environment variable)
267+5. `runtime/` subdirectory of path to Helix executable.
268+
269+This order also sets the priority for selecting which file will be used if multiple runtime
270+directories have files with the same name.
271+
272+#### Note to packagers
273+
274+If you are making a package of Helix for end users, to provide a good out of
275+the box experience, you should set the `HELIX_DEFAULT_RUNTIME` environment
276+variable at build time (before invoking `cargo build`) to a directory which
277+will store the final runtime files after installation. For example, say you want
278+to package the runtime into `/usr/lib/helix/runtime`. The rough steps a build
279+script could follow are:
280+
281+1. `export HELIX_DEFAULT_RUNTIME=/usr/lib/helix/runtime`
282+1. `cargo build --profile opt --locked --path helix-term`
283+1. `cp -r runtime $BUILD_DIR/usr/lib/helix/`
284+1. `cp target/opt/hx $BUILD_DIR/usr/bin/hx`
285+
286+This way the resulting `hx` binary will always look for its runtime directory in
287+`/usr/lib/helix/runtime` if the user has no custom runtime in `~/.config/helix`
288+or `HELIX_RUNTIME`.
289+
290+### Validating the installation
291+
292+To make sure everything is set up as expected you should run the Helix health
293+check:
294+
295+```sh
296+hx --health
297+```
298+
299+For more information on the health check results refer to
300+[Health check](https://github.com/helix-editor/helix/wiki/Healthcheck).
301+
302+### Configure the desktop shortcut
303+
304+If your desktop environment supports the
305+[XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html)
306+you can configure Helix to show up in the application menu by copying the
307+provided `.desktop` and icon files to their correct folders:
308+
309+```sh
310+cp contrib/Helix.desktop ~/.local/share/applications
311+cp contrib/helix.png ~/.icons # or ~/.local/share/icons
312+```
313+
314+To use another terminal than the system default, you can modify the `.desktop`
315+file. For example, to use `kitty`:
316+
317+```sh
318+sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
319+sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
320+```
book/src/keymap.mdnew file+454
@@ -0,0 +1,454 @@
1+# Keymap
2+
3+- [Normal mode](#normal-mode)
4+ - [Movement](#movement)
5+ - [Changes](#changes)
6+ - [Shell](#shell)
7+ - [Selection manipulation](#selection-manipulation)
8+ - [Search](#search)
9+ - [Minor modes](#minor-modes)
10+ - [View mode](#view-mode)
11+ - [Goto mode](#goto-mode)
12+ - [Match mode](#match-mode)
13+ - [Window mode](#window-mode)
14+ - [Space mode](#space-mode)
15+ - [Popup](#popup)
16+ - [Unimpaired](#unimpaired)
17+- [Insert mode](#insert-mode)
18+- [Select / extend mode](#select--extend-mode)
19+- [Picker](#picker)
20+- [Prompt](#prompt)
21+
22+> 💡 Mappings marked (**LSP**) require an active language server for the file.
23+
24+> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the file type.
25+
26+## Normal mode
27+
28+Normal mode is the default mode when you launch helix. You can return to it from other modes by pressing the `Escape` key.
29+
30+### Movement
31+
32+> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
33+
34+| Key | Description | Command |
35+| ----- | ----------- | ------- |
36+| `h`, `Left` | Move left | `move_char_left` |
37+| `j`, `Down` | Move down | `move_visual_line_down` |
38+| `k`, `Up` | Move up | `move_visual_line_up` |
39+| `l`, `Right` | Move right | `move_char_right` |
40+| `w` | Move next word start | `move_next_word_start` |
41+| `b` | Move previous word start | `move_prev_word_start` |
42+| `e` | Move next word end | `move_next_word_end` |
43+| `W` | Move next WORD start | `move_next_long_word_start` |
44+| `B` | Move previous WORD start | `move_prev_long_word_start` |
45+| `E` | Move next WORD end | `move_next_long_word_end` |
46+| `t` | Find 'till next char | `find_till_char` |
47+| `f` | Find next char | `find_next_char` |
48+| `T` | Find 'till previous char | `till_prev_char` |
49+| `F` | Find previous char | `find_prev_char` |
50+| `G` | Go to line number `<n>` | `goto_line` |
51+| `Alt-.` | Repeat last motion (`f`, `t` or `m`) | `repeat_last_motion` |
52+| `Home` | Move to the start of the line | `goto_line_start` |
53+| `End` | Move to the end of the line | `goto_line_end` |
54+| `Ctrl-b`, `PageUp` | Move page up | `page_up` |
55+| `Ctrl-f`, `PageDown` | Move page down | `page_down` |
56+| `Ctrl-u` | Move half page up | `half_page_up` |
57+| `Ctrl-d` | Move half page down | `half_page_down` |
58+| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
59+| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
60+| `Ctrl-s` | Save the current selection to the jumplist | `save_selection` |
61+
62+### Changes
63+
64+| Key | Description | Command |
65+| ----- | ----------- | ------- |
66+| `r` | Replace with a character | `replace` |
67+| `R` | Replace with yanked text | `replace_with_yanked` |
68+| `~` | Switch case of the selected text | `switch_case` |
69+| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` |
70+| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` |
71+| `i` | Insert before selection | `insert_mode` |
72+| `a` | Insert after selection (append) | `append_mode` |
73+| `I` | Insert at the start of the line | `insert_at_line_start` |
74+| `A` | Insert at the end of the line | `insert_at_line_end` |
75+| `o` | Open new line below selection | `open_below` |
76+| `O` | Open new line above selection | `open_above` |
77+| `.` | Repeat last insert | N/A |
78+| `u` | Undo change | `undo` |
79+| `U` | Redo change | `redo` |
80+| `Alt-u` | Move backward in history | `earlier` |
81+| `Alt-U` | Move forward in history | `later` |
82+| `y` | Yank selection | `yank` |
83+| `p` | Paste after selection | `paste_after` |
84+| `P` | Paste before selection | `paste_before` |
85+| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
86+| `>` | Indent selection | `indent` |
87+| `<` | Unindent selection | `unindent` |
88+| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
89+| `d` | Delete selection | `delete_selection` |
90+| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
91+| `c` | Change selection (delete and enter insert mode) | `change_selection` |
92+| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
93+| `Ctrl-a` | Increment object (number) under cursor | `increment` |
94+| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
95+| `Q` | Start/stop macro recording to the selected register (experimental) | `record_macro` |
96+| `q` | Play back a recorded macro from the selected register (experimental) | `replay_macro` |
97+
98+#### Shell
99+
100+| Key | Description | Command |
101+| ------ | ----------- | ------- |
102+| <code>&#124;</code> | Pipe each selection through shell command, replacing with output | `shell_pipe` |
103+| <code>Alt-&#124;</code> | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
104+| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
105+| `Alt-!` | Run shell command, appending output after each selection | `shell_append_output` |
106+| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
107+
108+
109+### Selection manipulation
110+
111+| Key | Description | Command |
112+| ----- | ----------- | ------- |
113+| `s` | Select all regex matches inside selections | `select_regex` |
114+| `S` | Split selection into sub selections on regex matches | `split_selection` |
115+| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
116+| `Alt-minus` | Merge selections | `merge_selections` |
117+| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
118+| `&` | Align selection in columns | `align_selections` |
119+| `_` | Trim whitespace from the selection | `trim_selections` |
120+| `;` | Collapse selection onto a single cursor | `collapse_selection` |
121+| `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
122+| `Alt-:` | Ensures the selection is in forward direction | `ensure_selections_forward` |
123+| `,` | Keep only the primary selection | `keep_primary_selection` |
124+| `Alt-,` | Remove the primary selection | `remove_primary_selection` |
125+| `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` |
126+| `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` |
127+| `(` | Rotate main selection backward | `rotate_selections_backward` |
128+| `)` | Rotate main selection forward | `rotate_selections_forward` |
129+| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
130+| `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` |
131+| `%` | Select entire file | `select_all` |
132+| `x` | Select current line, if already selected, extend to next line | `extend_line_below` |
133+| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
134+| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
135+| `J` | Join lines inside selection | `join_selections` |
136+| `Alt-J` | Join lines inside selection and select the inserted space | `join_selections_space` |
137+| `K` | Keep selections matching the regex | `keep_selections` |
138+| `Alt-K` | Remove selections matching the regex | `remove_selections` |
139+| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
140+| `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
141+| `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
142+| `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
143+| `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
144+
145+### Search
146+
147+Search commands all operate on the `/` register by default. To use a different register, use `"<char>`.
148+
149+| Key | Description | Command |
150+| ----- | ----------- | ------- |
151+| `/` | Search for regex pattern | `search` |
152+| `?` | Search for previous pattern | `rsearch` |
153+| `n` | Select next search match | `search_next` |
154+| `N` | Select previous search match | `search_prev` |
155+| `*` | Use current selection as the search pattern | `search_selection` |
156+
157+### Minor modes
158+
159+These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.
160+
161+| Key | Description | Command |
162+| ----- | ----------- | ------- |
163+| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
164+| `g` | Enter [goto mode](#goto-mode) | N/A |
165+| `m` | Enter [match mode](#match-mode) | N/A |
166+| `:` | Enter command mode | `command_mode` |
167+| `z` | Enter [view mode](#view-mode) | N/A |
168+| `Z` | Enter sticky [view mode](#view-mode) | N/A |
169+| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
170+| `Space` | Enter [space mode](#space-mode) | N/A |
171+
172+These modes (except command mode) can be configured by
173+[remapping keys](https://docs.helix-editor.com/remapping.html#minor-modes).
174+
175+#### View mode
176+
177+Accessed by typing `z` in [normal mode](#normal-mode).
178+
179+View mode is intended for scrolling and manipulating the view without changing
180+the selection. The "sticky" variant of this mode (accessed by typing `Z` in
181+normal mode) is persistent and can be exited using the escape key. This is
182+useful when you're simply looking over text and not actively editing it.
183+
184+
185+| Key | Description | Command |
186+| ----- | ----------- | ------- |
187+| `z`, `c` | Vertically center the line | `align_view_center` |
188+| `t` | Align the line to the top of the screen | `align_view_top` |
189+| `b` | Align the line to the bottom of the screen | `align_view_bottom` |
190+| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` |
191+| `j`, `down` | Scroll the view downwards | `scroll_down` |
192+| `k`, `up` | Scroll the view upwards | `scroll_up` |
193+| `Ctrl-f`, `PageDown` | Move page down | `page_down` |
194+| `Ctrl-b`, `PageUp` | Move page up | `page_up` |
195+| `Ctrl-d` | Move half page down | `half_page_down` |
196+| `Ctrl-u` | Move half page up | `half_page_up` |
197+
198+#### Goto mode
199+
200+Accessed by typing `g` in [normal mode](#normal-mode).
201+
202+Jumps to various locations.
203+
204+| Key | Description | Command |
205+| ----- | ----------- | ------- |
206+| `g` | Go to line number `<n>` else start of file | `goto_file_start` |
207+| `e` | Go to the end of the file | `goto_last_line` |
208+| `f` | Go to files in the selection | `goto_file` |
209+| `h` | Go to the start of the line | `goto_line_start` |
210+| `l` | Go to the end of the line | `goto_line_end` |
211+| `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` |
212+| `t` | Go to the top of the screen | `goto_window_top` |
213+| `c` | Go to the middle of the screen | `goto_window_center` |
214+| `b` | Go to the bottom of the screen | `goto_window_bottom` |
215+| `d` | Go to definition (**LSP**) | `goto_definition` |
216+| `y` | Go to type definition (**LSP**) | `goto_type_definition` |
217+| `r` | Go to references (**LSP**) | `goto_reference` |
218+| `i` | Go to implementation (**LSP**) | `goto_implementation` |
219+| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` |
220+| `m` | Go to the last modified/alternate file | `goto_last_modified_file` |
221+| `n` | Go to next buffer | `goto_next_buffer` |
222+| `p` | Go to previous buffer | `goto_previous_buffer` |
223+| `.` | Go to last modification in current file | `goto_last_modification` |
224+| `j` | Move down textual (instead of visual) line | `move_line_down` |
225+| `k` | Move up textual (instead of visual) line | `move_line_up` |
226+
227+#### Match mode
228+
229+Accessed by typing `m` in [normal mode](#normal-mode).
230+
231+See the relevant section in [Usage](./usage.md) for an explanation about
232+[surround](./usage.md#surround) and [textobject](./usage.md#navigating-using-tree-sitter-textobjects) usage.
233+
234+| Key | Description | Command |
235+| ----- | ----------- | ------- |
236+| `m` | Goto matching bracket (**TS**) | `match_brackets` |
237+| `s` `<char>` | Surround current selection with `<char>` | `surround_add` |
238+| `r` `<from><to>` | Replace surround character `<from>` with `<to>` | `surround_replace` |
239+| `d` `<char>` | Delete surround character `<char>` | `surround_delete` |
240+| `a` `<object>` | Select around textobject | `select_textobject_around` |
241+| `i` `<object>` | Select inside textobject | `select_textobject_inner` |
242+
243+TODO: Mappings for selecting syntax nodes (a superset of `[`).
244+
245+#### Window mode
246+
247+Accessed by typing `Ctrl-w` in [normal mode](#normal-mode).
248+
249+This layer is similar to Vim keybindings as Kakoune does not support windows.
250+
251+| Key | Description | Command |
252+| ----- | ------------- | ------- |
253+| `w`, `Ctrl-w` | Switch to next window | `rotate_view` |
254+| `v`, `Ctrl-v` | Vertical right split | `vsplit` |
255+| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` |
256+| `f` | Go to files in the selection in horizontal splits | `goto_file` |
257+| `F` | Go to files in the selection in vertical splits | `goto_file` |
258+| `h`, `Ctrl-h`, `Left` | Move to left split | `jump_view_left` |
259+| `j`, `Ctrl-j`, `Down` | Move to split below | `jump_view_down` |
260+| `k`, `Ctrl-k`, `Up` | Move to split above | `jump_view_up` |
261+| `l`, `Ctrl-l`, `Right` | Move to right split | `jump_view_right` |
262+| `q`, `Ctrl-q` | Close current window | `wclose` |
263+| `o`, `Ctrl-o` | Only keep the current window, closing all the others | `wonly` |
264+| `H` | Swap window to the left | `swap_view_left` |
265+| `J` | Swap window downwards | `swap_view_down` |
266+| `K` | Swap window upwards | `swap_view_up` |
267+| `L` | Swap window to the right | `swap_view_right` |
268+
269+#### Space mode
270+
271+Accessed by typing `Space` in [normal mode](#normal-mode).
272+
273+This layer is a kludge of mappings, mostly pickers.
274+
275+| Key | Description | Command |
276+| ----- | ----------- | ------- |
277+| `f` | Open file picker | `file_picker` |
278+| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
279+| `b` | Open buffer picker | `buffer_picker` |
280+| `j` | Open jumplist picker | `jumplist_picker` |
281+| `g` | Debug (experimental) | N/A |
282+| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
283+| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
284+| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
285+| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
286+| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
287+| `r` | Rename symbol (**LSP**) | `rename_symbol` |
288+| `a` | Apply code action (**LSP**) | `code_action` |
289+| `h` | Select symbol references (**LSP**) | `select_references_to_symbol_under_cursor` |
290+| `'` | Open last fuzzy picker | `last_picker` |
291+| `w` | Enter [window mode](#window-mode) | N/A |
292+| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
293+| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
294+| `y` | Yank selections to clipboard | `yank_to_clipboard` |
295+| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
296+| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
297+| `/` | Global search in workspace folder | `global_search` |
298+| `?` | Open command palette | `command_palette` |
299+
300+> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
301+
302+##### Popup
303+
304+Displays documentation for item under cursor.
305+
306+| Key | Description |
307+| ---- | ----------- |
308+| `Ctrl-u` | Scroll up |
309+| `Ctrl-d` | Scroll down |
310+
311+#### Unimpaired
312+
313+These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
314+
315+| Key | Description | Command |
316+| ----- | ----------- | ------- |
317+| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` |
318+| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
319+| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
320+| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` |
321+| `]f` | Go to next function (**TS**) | `goto_next_function` |
322+| `[f` | Go to previous function (**TS**) | `goto_prev_function` |
323+| `]t` | Go to next type definition (**TS**) | `goto_next_class` |
324+| `[t` | Go to previous type definition (**TS**) | `goto_prev_class` |
325+| `]a` | Go to next argument/parameter (**TS**) | `goto_next_parameter` |
326+| `[a` | Go to previous argument/parameter (**TS**) | `goto_prev_parameter` |
327+| `]c` | Go to next comment (**TS**) | `goto_next_comment` |
328+| `[c` | Go to previous comment (**TS**) | `goto_prev_comment` |
329+| `]T` | Go to next test (**TS**) | `goto_next_test` |
330+| `[T` | Go to previous test (**TS**) | `goto_prev_test` |
331+| `]p` | Go to next paragraph | `goto_next_paragraph` |
332+| `[p` | Go to previous paragraph | `goto_prev_paragraph` |
333+| `]g` | Go to next change | `goto_next_change` |
334+| `[g` | Go to previous change | `goto_prev_change` |
335+| `]G` | Go to last change | `goto_last_change` |
336+| `[G` | Go to first change | `goto_first_change` |
337+| `]Space` | Add newline below | `add_newline_below` |
338+| `[Space` | Add newline above | `add_newline_above` |
339+
340+## Insert mode
341+
342+Accessed by typing `i` in [normal mode](#normal-mode).
343+
344+Insert mode bindings are minimal by default. Helix is designed to
345+be a modal editor, and this is reflected in the user experience and internal
346+mechanics. Changes to the text are only saved for undos when
347+escaping from insert mode to normal mode.
348+
349+> 💡 New users are strongly encouraged to learn the modal editing paradigm
350+> to get the smoothest experience.
351+
352+| Key | Description | Command |
353+| ----- | ----------- | ------- |
354+| `Escape` | Switch to normal mode | `normal_mode` |
355+| `Ctrl-s` | Commit undo checkpoint | `commit_undo_checkpoint` |
356+| `Ctrl-x` | Autocomplete | `completion` |
357+| `Ctrl-r` | Insert a register content | `insert_register` |
358+| `Ctrl-w`, `Alt-Backspace` | Delete previous word | `delete_word_backward` |
359+| `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` |
360+| `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
361+| `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
362+| `Ctrl-h`, `Backspace`, `Shift-Backspace` | Delete previous char | `delete_char_backward` |
363+| `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` |
364+| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
365+
366+These keys are not recommended, but are included for new users less familiar
367+with modal editors.
368+
369+| Key | Description | Command |
370+| ----- | ----------- | ------- |
371+| `Up` | Move to previous line | `move_line_up` |
372+| `Down` | Move to next line | `move_line_down` |
373+| `Left` | Backward a char | `move_char_left` |
374+| `Right` | Forward a char | `move_char_right` |
375+| `PageUp` | Move one page up | `page_up` |
376+| `PageDown` | Move one page down | `page_down` |
377+| `Home` | Move to line start | `goto_line_start` |
378+| `End` | Move to line end | `goto_line_end_newline` |
379+
380+As you become more comfortable with modal editing, you may want to disable some
381+insert mode bindings. You can do this by editing your `config.toml` file.
382+
383+```toml
384+[keys.insert]
385+up = "no_op"
386+down = "no_op"
387+left = "no_op"
388+right = "no_op"
389+pageup = "no_op"
390+pagedown = "no_op"
391+home = "no_op"
392+end = "no_op"
393+```
394+
395+## Select / extend mode
396+
397+Accessed by typing `v` in [normal mode](#normal-mode).
398+
399+Select mode echoes Normal mode, but changes any movements to extend
400+selections rather than replace them. Goto motions are also changed to
401+extend, so that `vgl`, for example, extends the selection to the end of
402+the line.
403+
404+Search is also affected. By default, `n` and `N` will remove the current
405+selection and select the next instance of the search term. Toggling this
406+mode before pressing `n` or `N` makes it possible to keep the current
407+selection. Toggling it on and off during your iterative searching allows
408+you to selectively add search terms to your selections.
409+
410+## Picker
411+
412+Keys to use within picker. Remapping currently not supported.
413+
414+| Key | Description |
415+| ----- | ------------- |
416+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
417+| `Tab`, `Down`, `Ctrl-n` | Next entry |
418+| `PageUp`, `Ctrl-u` | Page up |
419+| `PageDown`, `Ctrl-d` | Page down |
420+| `Home` | Go to first entry |
421+| `End` | Go to last entry |
422+| `Enter` | Open selected |
423+| `Alt-Enter` | Open selected in the background without closing the picker |
424+| `Ctrl-s` | Open horizontally |
425+| `Ctrl-v` | Open vertically |
426+| `Ctrl-t` | Toggle preview |
427+| `Escape`, `Ctrl-c` | Close picker |
428+
429+## Prompt
430+
431+Keys to use within prompt, Remapping currently not supported.
432+
433+| Key | Description |
434+| ----- | ------------- |
435+| `Escape`, `Ctrl-c` | Close prompt |
436+| `Alt-b`, `Ctrl-Left` | Backward a word |
437+| `Ctrl-b`, `Left` | Backward a char |
438+| `Alt-f`, `Ctrl-Right` | Forward a word |
439+| `Ctrl-f`, `Right` | Forward a char |
440+| `Ctrl-e`, `End` | Move prompt end |
441+| `Ctrl-a`, `Home` | Move prompt start |
442+| `Ctrl-w`, `Alt-Backspace`, `Ctrl-Backspace` | Delete previous word |
443+| `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word |
444+| `Ctrl-u` | Delete to start of line |
445+| `Ctrl-k` | Delete to end of line |
446+| `Backspace`, `Ctrl-h`, `Shift-Backspace` | Delete previous char |
447+| `Delete`, `Ctrl-d` | Delete next char |
448+| `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
449+| `Ctrl-p`, `Up` | Select previous history |
450+| `Ctrl-n`, `Down` | Select next history |
451+| `Ctrl-r` | Insert the content of the register selected by following input char |
452+| `Tab` | Select next completion item |
453+| `BackTab` | Select previous completion item |
454+| `Enter` | Open selected |
book/src/lang-support.mdnew file+16
@@ -0,0 +1,16 @@
1+# Language Support
2+
3+The following languages and Language Servers are supported. To use
4+Language Server features, you must first [install][lsp-install-wiki] the
5+appropriate Language Server.
6+
7+You can check the language support in your installed helix version with `hx --health`.
8+
9+Also see the [Language Configuration][lang-config] docs and the [Adding
10+Languages][adding-languages] guide for more language configuration information.
11+
12+{{#include ./generated/lang-support.md}}
13+
14+[lsp-install-wiki]: https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers
15+[lang-config]: ./languages.md
16+[adding-languages]: ./guides/adding_languages.md
book/src/languages.mdnew file+237
@@ -0,0 +1,237 @@
1+# Languages
2+
3+Language-specific settings and settings for language servers are configured
4+in `languages.toml` files.
5+
6+## `languages.toml` files
7+
8+There are three possible locations for a `languages.toml` file:
9+
10+1. In the Helix source code, which lives in the
11+ [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
12+ It provides the default configurations for languages and language servers.
13+
14+2. In your [configuration directory](./configuration.md). This overrides values
15+ from the built-in language configuration. For example, to disable
16+ auto-LSP-formatting in Rust:
17+
18+ ```toml
19+ # in <config_dir>/helix/languages.toml
20+
21+ [language-server.mylang-lsp]
22+ command = "mylang-lsp"
23+
24+ [[language]]
25+ name = "rust"
26+ auto-format = false
27+ ```
28+
29+3. In a `.helix` folder in your project. Language configuration may also be
30+ overridden local to a project by creating a `languages.toml` file in a
31+ `.helix` folder. Its settings will be merged with the language configuration
32+ in the configuration directory and the built-in configuration.
33+
34+## Language configuration
35+
36+Each language is configured by adding a `[[language]]` section to a
37+`languages.toml` file. For example:
38+
39+```toml
40+[[language]]
41+name = "mylang"
42+scope = "source.mylang"
43+injection-regex = "mylang"
44+file-types = ["mylang", "myl"]
45+comment-token = "#"
46+indent = { tab-width = 2, unit = " " }
47+formatter = { command = "mylang-formatter" , args = ["--stdin"] }
48+language-servers = [ "mylang-lsp" ]
49+```
50+
51+These configuration keys are available:
52+
53+| Key | Description |
54+| ---- | ----------- |
55+| `name` | The name of the language |
56+| `language-id` | The language-id for language servers, checkout the table at [TextDocumentItem](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem) for the right id |
57+| `scope` | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
58+| `injection-regex` | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
59+| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
60+| `shebangs` | The interpreters from the shebang line, for example `["sh", "bash"]` |
61+| `roots` | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
62+| `auto-format` | Whether to autoformat this language when saving |
63+| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
64+| `comment-token` | The token to use as a comment-token |
65+| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
66+| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
67+| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
68+| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
69+| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
70+| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
71+| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
72+| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
73+
74+### File-type detection and the `file-types` key
75+
76+Helix determines which language configuration to use based on the `file-types` key
77+from the above section. `file-types` is a list of strings or tables, for
78+example:
79+
80+```toml
81+file-types = ["Makefile", "toml", { suffix = ".git/config" }]
82+```
83+
84+When determining a language configuration to use, Helix searches the file-types
85+with the following priorities:
86+
87+1. Exact match: if the filename of a file is an exact match of a string in a
88+ `file-types` list, that language wins. In the example above, `"Makefile"`
89+ will match against `Makefile` files.
90+2. Extension: if there are no exact matches, any `file-types` string that
91+ matches the file extension of a given file wins. In the example above, the
92+ `"toml"` matches files like `Cargo.toml` or `languages.toml`.
93+3. Suffix: if there are still no matches, any values in `suffix` tables
94+ are checked against the full path of the given file. In the example above,
95+ the `{ suffix = ".git/config" }` would match against any `config` files
96+ in `.git` directories. Note: `/` is used as the directory separator but is
97+ replaced at runtime with the appropriate path separator for the operating
98+ system, so this rule would match against `.git\config` files on Windows.
99+
100+## Language Server configuration
101+
102+Language servers are configured separately in the table `language-server` in the same file as the languages `languages.toml`
103+
104+For example:
105+
106+```toml
107+[language-server.mylang-lsp]
108+command = "mylang-lsp"
109+args = ["--stdio"]
110+config = { provideFormatter = true }
111+environment = { "ENV1" = "value1", "ENV2" = "value2" }
112+
113+[language-server.efm-lsp-prettier]
114+command = "efm-langserver"
115+
116+[language-server.efm-lsp-prettier.config]
117+documentFormatting = true
118+languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] }
119+```
120+
121+These are the available options for a language server.
122+
123+| Key | Description |
124+| ---- | ----------- |
125+| `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` |
126+| `args` | A list of arguments to pass to the language server binary |
127+| `config` | LSP initialization options |
128+| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
129+| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
130+
131+A `format` sub-table within `config` can be used to pass extra formatting options to
132+[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
133+For example, with typescript:
134+
135+```toml
136+[language-server.typescript-language-server]
137+# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
138+config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
139+```
140+
141+### Configuring Language Servers for a language
142+
143+The `language-servers` attribute in a language tells helix which language servers are used for this language.
144+
145+They have to be defined in the `[language-server]` table as described in the previous section.
146+
147+Different languages can use the same language server instance, e.g. `typescript-language-server` is used for javascript, jsx, tsx and typescript by default.
148+
149+In case multiple language servers are specified in the `language-servers` attribute of a `language`,
150+it's often useful to only enable/disable certain language-server features for these language servers.
151+
152+As an example, `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
153+so everything else should be handled by the `typescript-language-server` (which is configured by default).
154+The language configuration for typescript could look like this:
155+
156+```toml
157+[[language]]
158+name = "typescript"
159+language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ]
160+```
161+
162+or equivalent:
163+
164+```toml
165+[[language]]
166+name = "typescript"
167+language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ]
168+```
169+
170+Each requested LSP feature is prioritized in the order of the `language-servers` array.
171+For example, the first `goto-definition` supported language server (in this case `typescript-language-server`) will be taken for the relevant LSP request (command `goto_definition`).
172+The features `diagnostics`, `code-action`, `completion`, `document-symbols` and `workspace-symbols` are an exception to that rule, as they are working for all language servers at the same time and are merged together, if enabled for the language.
173+If no `except-features` or `only-features` is given, all features for the language server are enabled.
174+If a language server itself doesn't support a feature, the next language server array entry will be tried (and so on).
175+
176+The list of supported features is:
177+
178+- `format`
179+- `goto-definition`
180+- `goto-declaration`
181+- `goto-type-definition`
182+- `goto-reference`
183+- `goto-implementation`
184+- `signature-help`
185+- `hover`
186+- `document-highlight`
187+- `completion`
188+- `code-action`
189+- `workspace-command`
190+- `document-symbols`
191+- `workspace-symbols`
192+- `diagnostics`
193+- `rename-symbol`
194+- `inlay-hints`
195+
196+## Tree-sitter grammar configuration
197+
198+The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
199+section in `languages.toml`. For example:
200+
201+```toml
202+[[grammar]]
203+name = "mylang"
204+source = { git = "https://github.com/example/mylang", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }
205+```
206+
207+Grammar configuration takes these keys:
208+
209+| Key | Description |
210+| --- | ----------- |
211+| `name` | The name of the tree-sitter grammar |
212+| `source` | The method of fetching the grammar - a table with a schema defined below |
213+
214+Where `source` is a table with either these keys when using a grammar from a
215+git repository:
216+
217+| Key | Description |
218+| --- | ----------- |
219+| `git` | A git remote URL from which the grammar should be cloned |
220+| `rev` | The revision (commit hash or tag) which should be fetched |
221+| `subpath` | A path within the grammar directory which should be built. Some grammar repositories host multiple grammars (for example `tree-sitter-typescript` and `tree-sitter-ocaml`) in subdirectories. This key is used to point `hx --grammar build` to the correct path for compilation. When omitted, the root of repository is used |
222+
223+### Choosing grammars
224+
225+You may use a top-level `use-grammars` key to control which grammars are
226+fetched and built when using `hx --grammar fetch` and `hx --grammar build`.
227+
228+```toml
229+# Note: this key must come **before** the [[language]] and [[grammar]] sections
230+use-grammars = { only = [ "rust", "c", "cpp" ] }
231+# or
232+use-grammars = { except = [ "yaml", "json" ] }
233+```
234+
235+When omitted, all grammars are fetched and built.
236+
237+[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
book/src/remapping.mdnew file+79
@@ -0,0 +1,79 @@
1+# Key remapping
2+
3+Helix currently supports one-way key remapping through a simple TOML configuration
4+file. (More powerful solutions such as rebinding via commands will be
5+available in the future).
6+
7+To remap keys, create a `config.toml` file in your `helix` configuration
8+directory (default `~/.config/helix` on Linux systems) with a structure like
9+this:
10+
11+```toml
12+# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
13+[keys.normal]
14+C-s = ":w" # Maps Ctrl-s to the typable command :w which is an alias for :write (save file)
15+C-o = ":open ~/.config/helix/config.toml" # Maps Ctrl-o to opening of the helix config file
16+a = "move_char_left" # Maps the 'a' key to the move_char_left command
17+w = "move_line_up" # Maps the 'w' key move_line_up
18+"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
19+g = { a = "code_action" } # Maps `ga` to show possible code actions
20+"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
21+
22+[keys.insert]
23+"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
24+j = { k = "normal_mode" } # Maps `jk` to exit insert mode
25+```
26+
27+## Minor modes
28+
29+Minor modes are accessed by pressing a key (usually from normal mode), giving access to dedicated bindings. Bindings
30+can be modified or added by nesting definitions.
31+
32+```toml
33+[keys.insert.j]
34+k = "normal_mode" # Maps `jk` to exit insert mode
35+
36+[keys.normal.g]
37+a = "code_action" # Maps `ga` to show possible code actions
38+
39+# invert `j` and `k` in view mode
40+[keys.normal.z]
41+j = "scroll_up"
42+k = "scroll_down"
43+
44+# create a new minor mode bound to `+`
45+[keys.normal."+"]
46+m = ":run-shell-command make"
47+c = ":run-shell-command cargo build"
48+t = ":run-shell-command cargo test"
49+```
50+
51+## Special keys and modifiers
52+
53+Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
54+`C-`, `S-` and `A-`. Special keys are encoded as follows:
55+
56+| Key name | Representation |
57+| --- | --- |
58+| Backspace | `"backspace"` |
59+| Space | `"space"` |
60+| Return/Enter | `"ret"` |
61+| \- | `"minus"` |
62+| Left | `"left"` |
63+| Right | `"right"` |
64+| Up | `"up"` |
65+| Down | `"down"` |
66+| Home | `"home"` |
67+| End | `"end"` |
68+| Page Up | `"pageup"` |
69+| Page Down | `"pagedown"` |
70+| Tab | `"tab"` |
71+| Delete | `"del"` |
72+| Insert | `"ins"` |
73+| Null | `"null"` |
74+| Escape | `"esc"` |
75+
76+Keys can be disabled by binding them to the `no_op` command.
77+
78+A list of commands is available in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation
79+ and in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
book/src/themes.mdnew file+337
@@ -0,0 +1,337 @@
1+# Themes
2+
3+To use a theme add `theme = "<name>"` to the top of your [`config.toml`](./configuration.md) file, or select it during runtime using `:theme <name>`.
4+
5+## Creating a theme
6+
7+Create a file with the name of your theme as the file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes` or `%AppData%\helix\themes` on Windows). The directory might have to be created beforehand.
8+
9+> 💡 The names "default" and "base16_default" are reserved for built-in themes
10+> and cannot be overridden by user-defined themes.
11+
12+### Overview
13+
14+Each line in the theme file is specified as below:
15+
16+```toml
17+key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }
18+```
19+
20+Where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline` the underline `style`/`color`, and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults.
21+
22+To specify only the foreground color:
23+
24+```toml
25+key = "#ffffff"
26+```
27+
28+If the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys).
29+
30+```toml
31+"key.key" = "#ffffff"
32+```
33+
34+For inspiration, you can find the default `theme.toml`
35+[here](https://github.com/helix-editor/helix/blob/master/theme.toml) and
36+user-submitted themes
37+[here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
38+
39+### Using the linter
40+
41+Use the supplied linting tool to check for errors and missing scopes:
42+
43+```sh
44+cargo xtask themelint onedark # replace onedark with <name>
45+```
46+
47+## The details of theme creation
48+
49+### Color palettes
50+
51+It's recommended to define a palette of named colors, and refer to them in the
52+configuration values in your theme. To do this, add a table called
53+`palette` to your theme file:
54+
55+```toml
56+"ui.background" = "white"
57+"ui.text" = "black"
58+
59+[palette]
60+white = "#ffffff"
61+black = "#000000"
62+```
63+
64+Keep in mind that the `[palette]` table includes all keys after its header,
65+so it should be defined after the normal theme options.
66+
67+The default palette uses the terminal's default 16 colors, and the colors names
68+are listed below. The `[palette]` section in the config file takes precedence
69+over it and is merged into the default palette.
70+
71+| Color Name |
72+| --- |
73+| `default` |
74+| `black` |
75+| `red` |
76+| `green` |
77+| `yellow` |
78+| `blue` |
79+| `magenta` |
80+| `cyan` |
81+| `gray` |
82+| `light-red` |
83+| `light-green` |
84+| `light-yellow` |
85+| `light-blue` |
86+| `light-magenta` |
87+| `light-cyan` |
88+| `light-gray` |
89+| `white` |
90+
91+### Modifiers
92+
93+The following values may be used as modifier, provided they are supported by
94+your terminal emulator.
95+
96+| Modifier |
97+| --- |
98+| `bold` |
99+| `dim` |
100+| `italic` |
101+| `underlined` |
102+| `slow_blink` |
103+| `rapid_blink` |
104+| `reversed` |
105+| `hidden` |
106+| `crossed_out` |
107+
108+> 💡 The `underlined` modifier is deprecated and only available for backwards compatibility.
109+> Its behavior is equivalent to setting `underline.style="line"`.
110+
111+### Underline style
112+
113+One of the following values may be used as a value for `underline.style`, providing it is
114+supported by your terminal emulator.
115+
116+| Modifier |
117+| --- |
118+| `line` |
119+| `curl` |
120+| `dashed` |
121+| `dotted` |
122+| `double_line` |
123+
124+
125+### Inheritance
126+
127+Extend other themes by setting the `inherits` property to an existing theme.
128+
129+```toml
130+inherits = "boo_berry"
131+
132+# Override the theming for "keyword"s:
133+"keyword" = { fg = "gold" }
134+
135+# Override colors in the palette:
136+[palette]
137+berry = "#2A2A4D"
138+```
139+
140+### Scopes
141+
142+The following is a list of scopes available to use for styling:
143+
144+#### Syntax highlighting
145+
146+These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
147+
148+When determining styling for a highlight, the longest matching theme key will be used. For example, if the highlight is `function.builtin.static`, the key `function.builtin` will be used instead of `function`.
149+
150+We use a similar set of scopes as
151+[Sublime Text](https://www.sublimetext.com/docs/scope_naming.html). See also
152+[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
153+
154+- `attribute` - Class attributes, HTML tag attributes
155+
156+- `type` - Types
157+ - `builtin` - Primitive types provided by the language (`int`, `usize`)
158+ - `parameter` - Generic type parameters (`T`)
159+ - `enum`
160+ - `variant`
161+- `constructor`
162+
163+- `constant` (TODO: constant.other.placeholder for `%v`)
164+ - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
165+ - `boolean`
166+ - `character`
167+ - `escape`
168+ - `numeric` (numbers)
169+ - `integer`
170+ - `float`
171+
172+- `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)?
173+ - `regexp` - Regular expressions
174+ - `special`
175+ - `path`
176+ - `url`
177+ - `symbol` - Erlang/Elixir atoms, Ruby symbols, Clojure keywords
178+
179+- `comment` - Code comments
180+ - `line` - Single line comments (`//`)
181+ - `block` - Block comments (e.g. (`/* */`)
182+ - `documentation` - Documentation comments (e.g. `///` in Rust)
183+
184+- `variable` - Variables
185+ - `builtin` - Reserved language variables (`self`, `this`, `super`, etc.)
186+ - `parameter` - Function parameters
187+ - `other`
188+ - `member` - Fields of composite data types (e.g. structs, unions)
189+
190+- `label`
191+
192+- `punctuation`
193+ - `delimiter` - Commas, colons
194+ - `bracket` - Parentheses, angle brackets, etc.
195+ - `special` - String interpolation brackets.
196+
197+- `keyword`
198+ - `control`
199+ - `conditional` - `if`, `else`
200+ - `repeat` - `for`, `while`, `loop`
201+ - `import` - `import`, `export`
202+ - `return`
203+ - `exception`
204+ - `operator` - `or`, `in`
205+ - `directive` - Preprocessor directives (`#if` in C)
206+ - `function` - `fn`, `func`
207+ - `storage` - Keywords describing how things are stored
208+ - `type` - The type of something, `class`, `function`, `var`, `let`, etc.
209+ - `modifier` - Storage modifiers like `static`, `mut`, `const`, `ref`, etc.
210+
211+- `operator` - `||`, `+=`, `>`
212+
213+- `function`
214+ - `builtin`
215+ - `method`
216+ - `macro`
217+ - `special` (preprocessor in C)
218+
219+- `tag` - Tags (e.g. `<body>` in HTML)
220+ - `builtin`
221+
222+- `namespace`
223+
224+- `special`
225+
226+- `markup`
227+ - `heading`
228+ - `marker`
229+ - `1`, `2`, `3`, `4`, `5`, `6` - heading text for h1 through h6
230+ - `list`
231+ - `unnumbered`
232+ - `numbered`
233+ - `checked`
234+ - `unchecked`
235+ - `bold`
236+ - `italic`
237+ - `strikethrough`
238+ - `link`
239+ - `url` - URLs pointed to by links
240+ - `label` - non-URL link references
241+ - `text` - URL and image descriptions in links
242+ - `quote`
243+ - `raw`
244+ - `inline`
245+ - `block`
246+
247+- `diff` - version control changes
248+ - `plus` - additions
249+ - `gutter` - gutter indicator
250+ - `minus` - deletions
251+ - `gutter` - gutter indicator
252+ - `delta` - modifications
253+ - `moved` - renamed or moved files/changes
254+ - `gutter` - gutter indicator
255+
256+#### Interface
257+
258+These scopes are used for theming the editor interface:
259+
260+- `markup`
261+ - `normal`
262+ - `completion` - for completion doc popup UI
263+ - `hover` - for hover popup UI
264+ - `heading`
265+ - `completion` - for completion doc popup UI
266+ - `hover` - for hover popup UI
267+ - `raw`
268+ - `inline`
269+ - `completion` - for completion doc popup UI
270+ - `hover` - for hover popup UI
271+
272+
273+| Key | Notes |
274+| --- | --- |
275+| `ui.background` | |
276+| `ui.background.separator` | Picker separator below input line |
277+| `ui.cursor` | |
278+| `ui.cursor.normal` | |
279+| `ui.cursor.insert` | |
280+| `ui.cursor.select` | |
281+| `ui.cursor.match` | Matching bracket etc. |
282+| `ui.cursor.primary` | Cursor with primary selection |
283+| `ui.cursor.primary.normal` | |
284+| `ui.cursor.primary.insert` | |
285+| `ui.cursor.primary.select` | |
286+| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter |
287+| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter |
288+| `ui.gutter` | Gutter |
289+| `ui.gutter.selected` | Gutter for the line the cursor is on |
290+| `ui.highlight.frameline` | Line at which debugging execution is paused at |
291+| `ui.linenr` | Line numbers |
292+| `ui.linenr.selected` | Line number for the line the cursor is on |
293+| `ui.statusline` | Statusline |
294+| `ui.statusline.inactive` | Statusline (unfocused document) |
295+| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
296+| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
297+| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
298+| `ui.statusline.separator` | Separator character in statusline |
299+| `ui.bufferline` | Style for the buffer line |
300+| `ui.bufferline.active` | Style for the active buffer in buffer line |
301+| `ui.bufferline.background` | Style for bufferline background |
302+| `ui.popup` | Documentation popups (e.g. Space + k) |
303+| `ui.popup.info` | Prompt for multiple key options |
304+| `ui.window` | Borderlines separating splits |
305+| `ui.help` | Description box for commands |
306+| `ui.text` | Default text style, command prompts, popup text, etc. |
307+| `ui.text.focus` | The currently selected line in the picker |
308+| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
309+| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
310+| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
311+| `ui.virtual.whitespace` | Visible whitespace characters |
312+| `ui.virtual.indent-guide` | Vertical indent width guides |
313+| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
314+| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) |
315+| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) |
316+| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
317+| `ui.menu` | Code and command completion menus |
318+| `ui.menu.selected` | Selected autocomplete item |
319+| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
320+| `ui.selection` | For selections in the editing area |
321+| `ui.selection.primary` | |
322+| `ui.highlight` | Highlighted lines in the picker preview |
323+| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
324+| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
325+| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
326+| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
327+| `warning` | Diagnostics warning (gutter) |
328+| `error` | Diagnostics error (gutter) |
329+| `info` | Diagnostics info (gutter) |
330+| `hint` | Diagnostics hint (gutter) |
331+| `diagnostic` | Diagnostics fallback style (editing area) |
332+| `diagnostic.hint` | Diagnostics hint (editing area) |
333+| `diagnostic.info` | Diagnostics info (editing area) |
334+| `diagnostic.warning` | Diagnostics warning (editing area) |
335+| `diagnostic.error` | Diagnostics error (editing area) |
336+
337+[editor-section]: ./configuration.md#editor-section
book/src/title-page.mdnew file+17
@@ -0,0 +1,17 @@
1+# Helix
2+
3+Docs for bleeding edge master can be found at
4+[https://docs.helix-editor.com/master](https://docs.helix-editor.com/master).
5+
6+See the [usage] section for a quick overview of the editor, [keymap]
7+section for all available keybindings and the [configuration] section
8+for defining custom keybindings, setting themes, etc.
9+For everything else (e.g., how to install supported language servers), see the [Helix Wiki].
10+
11+Refer the [FAQ] for common questions.
12+
13+[FAQ]: https://github.com/helix-editor/helix/wiki/FAQ
14+[usage]: ./usage.md
15+[keymap]: ./keymap.md
16+[configuration]: ./configuration.md
17+[Helix Wiki]: https://github.com/helix-editor/helix/wiki
book/src/usage.mdnew file+208
@@ -0,0 +1,208 @@
1+# Using Helix
2+
3+<!--toc:start-->
4+- [Registers](#registers)
5+ - [User-defined registers](#user-defined-registers)
6+ - [Special registers](#special-registers)
7+- [Surround](#surround)
8+- [Selecting and manipulating text with textobjects](#selecting-and-manipulating-text-with-textobjects)
9+- [Navigating using tree-sitter textobjects](#navigating-using-tree-sitter-textobjects)
10+- [Moving the selection with syntax-aware motions](#moving-the-selection-with-syntax-aware-motions)
11+<!--toc:end-->
12+
13+For a full interactive introduction to Helix, refer to the
14+[tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) which
15+can be accessed via the command `hx --tutor` or `:tutor`.
16+
17+> 💡 Currently, not all functionality is fully documented, please refer to the
18+> [key mappings](./keymap.md) list.
19+
20+## Registers
21+
22+In Helix, registers are storage locations for text and other data, such as the
23+result of a search. Registers can be used to cut, copy, and paste text, similar
24+to the clipboard in other text editors. Usage is similar to Vim, with `"` being
25+used to select a register.
26+
27+### User-defined registers
28+
29+Helix allows you to create your own named registers for storing text, for
30+example:
31+
32+- `"ay` - Yank the current selection to register `a`.
33+- `"op` - Paste the text in register `o` after the selection.
34+
35+If a register is selected before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:
36+
37+- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
38+- `"md` - Store the selection in register `m` and delete it.
39+
40+### Default registers
41+
42+Commands that use registers, like yank (`y`), use a default register if none is specified.
43+These registers are used as defaults:
44+
45+| Register character | Contains |
46+| --- | --- |
47+| `/` | Last search |
48+| `:` | Last executed command |
49+| `"` | Last yanked text |
50+| `@` | Last recorded macro |
51+
52+### Special registers
53+
54+Some registers have special behavior when read from and written to.
55+
56+| Register character | When read | When written |
57+| --- | --- | --- |
58+| `_` | No values are returned | All values are discarded |
59+| `#` | Selection indices (first selection is `1`, second is `2`, etc.) | This register is not writable |
60+| `.` | Contents of the current selections | This register is not writable |
61+| `%` | Name of the current file | This register is not writable |
62+| `+` | Reads from the system clipboard | Joins and yanks to the system clipboard |
63+| `*` | Reads from the primary clipboard | Joins and yanks to the primary clipboard |
64+
65+When yanking multiple selections to the clipboard registers, the selections
66+are joined with newlines. Pasting from these registers will paste multiple
67+selections if the clipboard was last yanked to by the Helix session. Otherwise
68+the clipboard contents are pasted as one selection.
69+
70+## Surround
71+
72+Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround).
73+The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
74+
75+![Surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif)
76+
77+| Key Sequence | Action |
78+| --------------------------------- | --------------------------------------- |
79+| `ms<char>` (after selecting text) | Add surround characters to selection |
80+| `mr<char_to_replace><new_char>` | Replace the closest surround characters |
81+| `md<char_to_delete>` | Delete the closest surround characters |
82+
83+You can use counts to act on outer pairs.
84+
85+Surround can also act on multiple selections. For example, to change every occurrence of `(use)` to `[use]`:
86+
87+1. `%` to select the whole file
88+2. `s` to split the selections on a search term
89+3. Input `use` and hit Enter
90+4. `mr([` to replace the parentheses with square brackets
91+
92+Multiple characters are currently not supported, but planned for future release.
93+
94+## Selecting and manipulating text with textobjects
95+
96+In Helix, textobjects are a way to select, manipulate and operate on a piece of
97+text in a structured way. They allow you to refer to blocks of text based on
98+their structure or purpose, such as a word, sentence, paragraph, or even a
99+function or block of code.
100+
101+![Textobject demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif)
102+![Textobject tree-sitter demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif)
103+
104+- `ma` - Select around the object (`va` in Vim, `<alt-a>` in Kakoune)
105+- `mi` - Select inside the object (`vi` in Vim, `<alt-i>` in Kakoune)
106+
107+| Key after `mi` or `ma` | Textobject selected |
108+| --- | --- |
109+| `w` | Word |
110+| `W` | WORD |
111+| `p` | Paragraph |
112+| `(`, `[`, `'`, etc. | Specified surround pairs |
113+| `m` | The closest surround pair |
114+| `f` | Function |
115+| `t` | Type (or Class) |
116+| `a` | Argument/parameter |
117+| `c` | Comment |
118+| `T` | Test |
119+| `g` | Change |
120+
121+> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
122+document and a special tree-sitter query file to work properly. [Only
123+some grammars][lang-support] currently have the query file implemented.
124+Contributions are welcome!
125+
126+## Navigating using tree-sitter textobjects
127+
128+Navigating between functions, classes, parameters, and other elements is
129+possible using tree-sitter and textobject queries. For
130+example to move to the next function use `]f`, to move to previous
131+type use `[t`, and so on.
132+
133+![Tree-sitter-nav-demo][tree-sitter-nav-demo]
134+
135+For the full reference see the [unimpaired][unimpaired-keybinds] section of the key bind
136+documentation.
137+
138+> 💡 This feature relies on tree-sitter textobjects
139+> and requires the corresponding query file to work properly.
140+
141+## Moving the selection with syntax-aware motions
142+
143+`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to move the
144+selection according to its location in the syntax tree. For example, many languages have the
145+following syntax for function calls:
146+
147+```js
148+func(arg1, arg2, arg3);
149+```
150+
151+A function call might be parsed by tree-sitter into a tree like the following.
152+
153+```tsq
154+(call
155+ function: (identifier) ; func
156+ arguments:
157+ (arguments ; (arg1, arg2, arg3)
158+ (identifier) ; arg1
159+ (identifier) ; arg2
160+ (identifier))) ; arg3
161+```
162+
163+Use `:tree-sitter-subtree` to view the syntax tree of the primary selection. In
164+a more intuitive tree format:
165+
166+```
167+ ┌────┐
168+ │call│
169+ ┌─────┴────┴─────┐
170+ │ │
171+┌─────▼────┐ ┌────▼────┐
172+│identifier│ │arguments│
173+│ "func" │ ┌────┴───┬─────┴───┐
174+└──────────┘ │ │ │
175+ │ │ │
176+ ┌─────────▼┐ ┌────▼─────┐ ┌▼─────────┐
177+ │identifier│ │identifier│ │identifier│
178+ │ "arg1" │ │ "arg2" │ │ "arg3" │
179+ └──────────┘ └──────────┘ └──────────┘
180+```
181+
182+If you have a selection that wraps `arg1` (see the tree above), and you use
183+`Alt-n`, it will select the next sibling in the syntax tree: `arg2`.
184+
185+```js
186+// before
187+func([arg1], arg2, arg3)
188+// after
189+func(arg1, [arg2], arg3);
190+```
191+
192+Similarly, `Alt-o` will expand the selection to the parent node, in this case, the
193+arguments node.
194+
195+```js
196+func[(arg1, arg2, arg3)];
197+```
198+
199+There is also some nuanced behavior that prevents you from getting stuck on a
200+node with no sibling. When using `Alt-p` with a selection on `arg1`, the previous
201+child node will be selected. In the event that `arg1` does not have a previous
202+sibling, the selection will move up the syntax tree and select the previous
203+element. As a result, using `Alt-p` with a selection on `arg1` will move the
204+selection to the "func" `identifier`.
205+
206+[lang-support]: ./lang-support.md
207+[unimpaired-keybinds]: ./keymap.md#unimpaired
208+[tree-sitter-nav-demo]: https://user-images.githubusercontent.com/23398472/152332550-7dfff043-36a2-4aec-b8f2-77c13eb56d6f.gif
book/theme/favicon.pngnew file

Binary file not shown.

book/theme/favicon.svgnew file+1
@@ -0,0 +1 @@
1+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" viewBox="663.38 37.57 575.35 903.75"> <g transform="matrix(1,0,0,1,-31352.7,-1817.25)"> <g transform="matrix(1,0,0,1,31062.7,-20.8972)"> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1083.58,1875.72L1635.06,2194.12C1649.8,2202.63 1658.88,2218.37 1658.88,2235.39C1658.88,2264.98 1658.88,2311.74 1658.88,2341.33C1658.88,2349.84 1656.61,2358.03 1652.5,2365.16C1652.5,2365.16 1214.7,2112.4 1107.2,2050.33C1092.58,2041.89 1083.58,2026.29 1083.58,2009.41C1083.58,1963.5 1083.58,1875.72 1083.58,1875.72Z" style="fill:#706bc8;"></path> </g> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1635.26,2604.84C1649.88,2613.28 1658.88,2628.87 1658.88,2645.75C1658.88,2691.67 1658.88,2779.44 1658.88,2779.44L1107.41,2461.05C1092.66,2452.53 1083.58,2436.8 1083.58,2419.78C1083.58,2390.19 1083.58,2343.42 1083.58,2313.84C1083.58,2305.32 1085.85,2297.13 1089.96,2290.01C1089.96,2290.01 1527.76,2542.77 1635.26,2604.84Z" style="fill:#55c5e4;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1432.56C785.214,1435.55 780.717,1439.9 777.509,1445.46C767.862,1462.16 773.473,1483.76 790.004,1493.59L789.998,1493.59L761.173,1476.95C746.427,1468.44 737.344,1452.71 737.344,1435.68C737.344,1406.09 737.344,1359.33 737.344,1329.74C737.344,1312.71 746.427,1296.98 761.173,1288.47L1259.59,1000.74L1259.83,1000.6C1264.92,997.617 1269.33,993.314 1272.48,987.844C1282.13,971.136 1276.52,949.544 1259.99,939.707L1260,939.707L1288.82,956.349C1303.57,964.862 1312.65,980.595 1312.65,997.622C1312.65,1027.21 1312.65,1073.97 1312.65,1103.56C1312.65,1120.59 1303.57,1136.32 1288.82,1144.83L1259.19,1161.94L1259.59,1161.68L790.407,1432.56Z" style="fill:#84ddea;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1686.24C785.214,1689.23 780.717,1693.58 777.509,1699.13C767.862,1715.84 773.473,1737.43 790.004,1747.27L789.998,1747.27L761.173,1730.63C746.427,1722.12 737.344,1706.38 737.344,1689.36C737.344,1659.77 737.344,1613.01 737.344,1583.42C737.344,1566.39 746.427,1550.66 761.173,1542.15L1259.59,1254.42L1259.83,1254.28C1264.92,1251.29 1269.33,1246.99 1272.48,1241.52C1282.13,1224.81 1276.52,1203.22 1259.99,1193.38L1260,1193.38L1288.82,1210.03C1303.57,1218.54 1312.65,1234.27 1312.65,1251.3C1312.65,1280.89 1312.65,1327.65 1312.65,1357.24C1312.65,1374.26 1303.57,1390 1288.82,1398.51L1259.19,1415.61L1259.59,1415.36L790.407,1686.24Z" style="fill:#997bc8;"></path></g></g></g> </svg>
book/theme/index.hbsnew file+345
@@ -0,0 +1,345 @@
1+<!DOCTYPE HTML>
2+<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
3+ <head>
4+ <!-- Book generated using mdBook -->
5+ <meta charset="UTF-8">
6+ <title>{{ title }}</title>
7+ {{#if is_print }}
8+ <meta name="robots" content="noindex">
9+ {{/if}}
10+ {{#if base_url}}
11+ <base href="{{ base_url }}">
12+ {{/if}}
13+
14+
15+ <!-- Custom HTML head -->
16+ {{> head}}
17+
18+ <meta name="description" content="{{ description }}">
19+ <meta name="viewport" content="width=device-width, initial-scale=1">
20+ <meta name="theme-color" content="#ffffff">
21+
22+ {{#if favicon_svg}}
23+ <link rel="icon" href="{{ path_to_root }}favicon.svg">
24+ {{/if}}
25+ {{#if favicon_png}}
26+ <link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
27+ {{/if}}
28+ <link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
29+ <link rel="stylesheet" href="{{ path_to_root }}css/general.css">
30+ <link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
31+ {{#if print_enable}}
32+ <link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
33+ {{/if}}
34+
35+ <!-- Fonts -->
36+ <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
37+ {{#if copy_fonts}}
38+ <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
39+ {{/if}}
40+
41+ <!-- Highlight.js Stylesheets -->
42+ <link rel="stylesheet" href="{{ path_to_root }}highlight.css">
43+ <link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
44+ <link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
45+
46+ <!-- Custom theme stylesheets -->
47+ {{#each additional_css}}
48+ <link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
49+ {{/each}}
50+
51+ {{#if mathjax_support}}
52+ <!-- MathJax -->
53+ <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
54+ {{/if}}
55+ </head>
56+ <body class="sidebar-visible no-js">
57+ <div id="body-container">
58+ <!-- Provide site root to javascript -->
59+ <script>
60+ var path_to_root = "{{ path_to_root }}";
61+ var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
62+ </script>
63+
64+ <!-- Work around some values being stored in localStorage wrapped in quotes -->
65+ <script>
66+ try {
67+ var theme = localStorage.getItem('mdbook-theme');
68+ var sidebar = localStorage.getItem('mdbook-sidebar');
69+
70+ if (theme.startsWith('"') && theme.endsWith('"')) {
71+ localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
72+ }
73+
74+ if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
75+ localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
76+ }
77+ } catch (e) { }
78+ </script>
79+
80+ <!-- Set the theme before any content is loaded, prevents flash -->
81+ <script>
82+ var theme;
83+ try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
84+ if (theme === null || theme === undefined) { theme = default_theme; }
85+ var html = document.querySelector('html');
86+ html.classList.remove('{{ default_theme }}')
87+ html.classList.add(theme);
88+ var body = document.querySelector('body');
89+ body.classList.remove('no-js')
90+ body.classList.add('js');
91+ </script>
92+
93+ <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
94+
95+ <!-- Hide / unhide sidebar before it is displayed -->
96+ <script>
97+ var body = document.querySelector('body');
98+ var sidebar = null;
99+ var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
100+ if (document.body.clientWidth >= 1080) {
101+ try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
102+ sidebar = sidebar || 'visible';
103+ } else {
104+ sidebar = 'hidden';
105+ }
106+ sidebar_toggle.checked = sidebar === 'visible';
107+ body.classList.remove('sidebar-visible');
108+ body.classList.add("sidebar-" + sidebar);
109+ </script>
110+
111+ <nav id="sidebar" class="sidebar" aria-label="Table of contents">
112+ <div class="sidebar-scrollbox">
113+ {{#toc}}{{/toc}}
114+ </div>
115+ <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
116+ </nav>
117+
118+ <!-- Track and set sidebar scroll position -->
119+ <script>
120+ var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
121+ sidebarScrollbox.addEventListener('click', function(e) {
122+ if (e.target.tagName === 'A') {
123+ sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
124+ }
125+ }, { passive: true });
126+ var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
127+ sessionStorage.removeItem('sidebar-scroll');
128+ if (sidebarScrollTop) {
129+ // preserve sidebar scroll position when navigating via links within sidebar
130+ sidebarScrollbox.scrollTop = sidebarScrollTop;
131+ } else {
132+ // scroll sidebar to current active section when navigating via "next/previous chapter" buttons
133+ var activeSection = document.querySelector('#sidebar .active');
134+ if (activeSection) {
135+ activeSection.scrollIntoView({ block: 'center' });
136+ }
137+ }
138+ </script>
139+
140+ <div id="page-wrapper" class="page-wrapper">
141+
142+ <div class="page">
143+ {{> header}}
144+ <div id="menu-bar-hover-placeholder"></div>
145+ <div id="menu-bar" class="menu-bar sticky">
146+ <div class="left-buttons">
147+ <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
148+ <i class="fa fa-bars"></i>
149+ </label>
150+ <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
151+ <i class="fa fa-paint-brush"></i>
152+ </button>
153+ <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
154+ <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
155+ <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
156+ <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
157+ <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
158+ <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
159+ <li role="none"><button role="menuitem" class="theme" id="colibri">Colibri</button></li>
160+ </ul>
161+ {{#if search_enabled}}
162+ <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
163+ <i class="fa fa-search"></i>
164+ </button>
165+ {{/if}}
166+ </div>
167+
168+ <h1 class="menu-title">{{ book_title }}</h1>
169+
170+ <div class="right-buttons">
171+ {{#if print_enable}}
172+ <a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
173+ <i id="print-button" class="fa fa-print"></i>
174+ </a>
175+ {{/if}}
176+ {{#if git_repository_url}}
177+ <a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
178+ <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
179+ </a>
180+ {{/if}}
181+ {{#if git_repository_edit_url}}
182+ <a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
183+ <i id="git-edit-button" class="fa fa-edit"></i>
184+ </a>
185+ {{/if}}
186+
187+ </div>
188+ </div>
189+
190+ {{#if search_enabled}}
191+ <div id="search-wrapper" class="hidden">
192+ <form id="searchbar-outer" class="searchbar-outer">
193+ <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
194+ </form>
195+ <div id="searchresults-outer" class="searchresults-outer hidden">
196+ <div id="searchresults-header" class="searchresults-header"></div>
197+ <ul id="searchresults">
198+ </ul>
199+ </div>
200+ </div>
201+ {{/if}}
202+
203+ <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
204+ <script>
205+ document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
206+ document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
207+ Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
208+ link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
209+ });
210+ </script>
211+
212+ <div id="content" class="content">
213+ <main>
214+ {{{ content }}}
215+ </main>
216+
217+ <nav class="nav-wrapper" aria-label="Page navigation">
218+ <!-- Mobile navigation buttons -->
219+ {{#previous}}
220+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
221+ <i class="fa fa-angle-left"></i>
222+ </a>
223+ {{/previous}}
224+
225+ {{#next}}
226+ <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
227+ <i class="fa fa-angle-right"></i>
228+ </a>
229+ {{/next}}
230+
231+ <div style="clear: both"></div>
232+ </nav>
233+ </div>
234+ </div>
235+
236+ <nav class="nav-wide-wrapper" aria-label="Page navigation">
237+ {{#previous}}
238+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
239+ <i class="fa fa-angle-left"></i>
240+ </a>
241+ {{/previous}}
242+
243+ {{#next}}
244+ <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
245+ <i class="fa fa-angle-right"></i>
246+ </a>
247+ {{/next}}
248+ </nav>
249+
250+ </div>
251+
252+ {{#if live_reload_endpoint}}
253+ <!-- Livereload script (if served using the cli tool) -->
254+ <script>
255+ const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
256+ const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
257+ const socket = new WebSocket(wsAddress);
258+ socket.onmessage = function (event) {
259+ if (event.data === "reload") {
260+ socket.close();
261+ location.reload();
262+ }
263+ };
264+
265+ window.onbeforeunload = function() {
266+ socket.close();
267+ }
268+ </script>
269+ {{/if}}
270+
271+ {{#if google_analytics}}
272+ <!-- Google Analytics Tag -->
273+ <script>
274+ var localAddrs = ["localhost", "127.0.0.1", ""];
275+
276+ // make sure we don't activate google analytics if the developer is
277+ // inspecting the book locally...
278+ if (localAddrs.indexOf(document.location.hostname) === -1) {
279+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
280+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
281+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
282+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
283+
284+ ga('create', '{{google_analytics}}', 'auto');
285+ ga('send', 'pageview');
286+ }
287+ </script>
288+ {{/if}}
289+
290+ {{#if playground_line_numbers}}
291+ <script>
292+ window.playground_line_numbers = true;
293+ </script>
294+ {{/if}}
295+
296+ {{#if playground_copyable}}
297+ <script>
298+ window.playground_copyable = true;
299+ </script>
300+ {{/if}}
301+
302+ {{#if playground_js}}
303+ <script src="{{ path_to_root }}ace.js"></script>
304+ <script src="{{ path_to_root }}editor.js"></script>
305+ <script src="{{ path_to_root }}mode-rust.js"></script>
306+ <script src="{{ path_to_root }}theme-dawn.js"></script>
307+ <script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
308+ {{/if}}
309+
310+ {{#if search_js}}
311+ <script src="{{ path_to_root }}elasticlunr.min.js"></script>
312+ <script src="{{ path_to_root }}mark.min.js"></script>
313+ <script src="{{ path_to_root }}searcher.js"></script>
314+ {{/if}}
315+
316+ <script src="{{ path_to_root }}clipboard.min.js"></script>
317+ <script src="{{ path_to_root }}highlight.js"></script>
318+ <script src="{{ path_to_root }}book.js"></script>
319+
320+ <!-- Custom JS scripts -->
321+ {{#each additional_js}}
322+ <script src="{{ ../path_to_root }}{{this}}"></script>
323+ {{/each}}
324+
325+ {{#if is_print}}
326+ {{#if mathjax_support}}
327+ <script>
328+ window.addEventListener('load', function() {
329+ MathJax.Hub.Register.StartupHook('End', function() {
330+ window.setTimeout(window.print, 100);
331+ });
332+ });
333+ </script>
334+ {{else}}
335+ <script>
336+ window.addEventListener('load', function() {
337+ window.setTimeout(window.print, 100);
338+ });
339+ </script>
340+ {{/if}}
341+ {{/if}}
342+
343+ </div>
344+ </body>
345+</html>
contrib/Helix.appdata.xmlnew file+108
@@ -0,0 +1,108 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<component type="desktop-application">
3+ <id>com.helix_editor.Helix</id>
4+ <metadata_license>CC0-1.0</metadata_license>
5+ <project_license>MPL-2.0</project_license>
6+ <name>Helix</name>
7+ <summary>A post-modern text editor</summary>
8+ <summary xml:lang="ar">مُحَرِّرُ نُصُوصٍ سَابِقٌ لِعَهدِه</summary>
9+
10+ <description>
11+ <p>
12+ Helix is a terminal-based text editor inspired by Kakoune / Neovim and written in Rust.
13+ </p>
14+ <ul>
15+ <li>Vim-like modal editing</li>
16+ <li>Multiple selections</li>
17+ <li>Built-in language server support</li>
18+ <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
19+ </ul>
20+ </description>
21+ <description xml:lang="ar">
22+ <p>
23+ مُحَرِّرُ نُصُوصٍ يَعمَلُ فِي الطَّرَفِيَّة، مُستَلهَمٌ مِن Kakoune وَ Neovim وَمَكتُوبٌ بِلُغَةِ رَست البَرمَجِيَّة.
24+ </p>
25+ <ul>
26+ <li>تَحرِيرٌ وَضعِيٌّ شَبيهٌ بِـVim</li>
27+ <li>تَحدِيدَاتٌ لِلنَّصِ مُتَعَدِّدَة</li>
28+ <li>دَعْمٌ مُدمَجٌ لِخَوادِمِ اللُّغَات</li>
29+ <li>تَحرِيرُ التَّعلِيمَاتِ البَّرمَجِيَّةِ مَعَ تَمييزٍ لِلتَّركِيبِ النَّحُويِّ بِواسِطَةِ tree-sitter</li>
30+ </ul>
31+ </description>
32+
33+ <launchable type="desktop-id">Helix.desktop</launchable>
34+
35+ <screenshots>
36+ <screenshot type="default">
37+ <caption>Helix with default theme</caption>
38+ <image>https://github.com/helix-editor/helix/raw/d4565b4404cabc522bd60822abd374755581d751/screenshot.png</image>
39+ </screenshot>
40+ </screenshots>
41+
42+ <url type="homepage">https://helix-editor.com/</url>
43+ <url type="donation">https://opencollective.com/helix-editor</url>
44+ <url type="help">https://docs.helix-editor.com/</url>
45+ <url type="vcs-browser">https://github.com/helix-editor/helix</url>
46+ <url type="bugtracker">https://github.com/helix-editor/helix/issues</url>
47+
48+ <content_rating type="oars-1.1" />
49+
50+ <releases>
51+ <release version="23.10" date="2023-10-24">
52+ <url>https://helix-editor.com/news/release-23-10-highlights/</url>
53+ </release>
54+ <release version="23.05" date="2023-05-18">
55+ <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
56+ </release>
57+ <release version="23.03" date="2023-03-31">
58+ <url>https://helix-editor.com/news/release-23-03-highlights/</url>
59+ </release>
60+ <release version="22.12" date="2022-12-6">
61+ <url>https://helix-editor.com/news/release-22-12-highlights/</url>
62+ </release>
63+ <release version="22.08" date="2022-8-31">
64+ <url>https://helix-editor.com/news/release-22-08-highlights/</url>
65+ </release>
66+ <release version="22.05" date="2022-5-28">
67+ <url>https://helix-editor.com/news/release-22-05-highlights/</url>
68+ </release>
69+ <release version="22.03" date="2022-3-28">
70+ <url>https://helix-editor.com/news/release-22-03-highlights/</url>
71+ </release>
72+ </releases>
73+
74+ <requires>
75+ <control>keyboard</control>
76+ </requires>
77+
78+ <categories>
79+ <category>Utility</category>
80+ <category>TextEditor</category>
81+ </categories>
82+
83+ <keywords>
84+ <keyword>text</keyword>
85+ <keyword>editor</keyword>
86+ <keyword>development</keyword>
87+ <keyword>programming</keyword>
88+ </keywords>
89+
90+ <provides>
91+ <binary>hx</binary>
92+ <mediatype>text/english</mediatype>
93+ <mediatype>text/plain</mediatype>
94+ <mediatype>text/x-makefile</mediatype>
95+ <mediatype>text/x-c++hdr</mediatype>
96+ <mediatype>text/x-c++src</mediatype>
97+ <mediatype>text/x-chdr</mediatype>
98+ <mediatype>text/x-csrc</mediatype>
99+ <mediatype>text/x-java</mediatype>
100+ <mediatype>text/x-moc</mediatype>
101+ <mediatype>text/x-pascal</mediatype>
102+ <mediatype>text/x-tcl</mediatype>
103+ <mediatype>text/x-tex</mediatype>
104+ <mediatype>application/x-shellscript</mediatype>
105+ <mediatype>text/x-c</mediatype>
106+ <mediatype>text/x-c++</mediatype>
107+ </provides>
108+</component>
contrib/Helix.desktopnew file+91
@@ -0,0 +1,91 @@
1+[Desktop Entry]
2+Name=Helix
3+GenericName=Text Editor
4+GenericName[ar]=مُحَرِّرُ نُصُوص
5+GenericName[de]=Texteditor
6+GenericName[fr]=Éditeur de texte
7+GenericName[ru]=Текстовый редактор
8+GenericName[sr]=Едитор текст
9+GenericName[tr]=Metin Düzenleyici
10+Comment=Edit text files
11+Comment[af]=Redigeer tekslêers
12+Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
13+Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
14+Comment[az]=Mətn fayllarını redaktə edin
15+Comment[be]=Рэдагаваньне тэкставых файлаў
16+Comment[bg]=Редактиране на текстови файлове
17+Comment[bn]=টেক্স্ট ফাইল এডিট করুন
18+Comment[bs]=Izmijeni tekstualne datoteke
19+Comment[ca]=Edita fitxers de text
20+Comment[cs]=Úprava textových souborů
21+Comment[cy]=Golygu ffeiliau testun
22+Comment[da]=Redigér tekstfiler
23+Comment[de]=Textdateien bearbeiten
24+Comment[el]=Επεξεργασία αρχείων κειμένου
25+Comment[en_CA]=Edit text files
26+Comment[en_GB]=Edit text files
27+Comment[es]=Edita archivos de texto
28+Comment[et]=Redigeeri tekstifaile
29+Comment[eu]=Editatu testu-fitxategiak
30+Comment[fa]=ویرایش پرونده‌های متنی
31+Comment[fi]=Muokkaa tekstitiedostoja
32+Comment[fr]=Éditer des fichiers texte
33+Comment[ga]=Eagar comhad Téacs
34+Comment[gu]=લખાણ ફાઇલોમાં ફેરફાર કરો
35+Comment[he]=ערוך קבצי טקסט
36+Comment[hi]=पाठ फ़ाइलें संपादित करें
37+Comment[hr]=Uređivanje tekstualne datoteke
38+Comment[hu]=Szövegfájlok szerkesztése
39+Comment[id]=Edit file teks
40+Comment[it]=Modifica file di testo
41+Comment[ja]=テキストファイルを編集します
42+Comment[kn]=ಪಠ್ಯ ಕಡತಗಳನ್ನು ಸಂಪಾದಿಸು
43+Comment[ko]=텍스트 파일을 편집합니다
44+Comment[lt]=Redaguoti tekstines bylas
45+Comment[lv]=Rediģēt teksta failus
46+Comment[mk]=Уреди текстуални фајлови
47+Comment[ml]=വാചക രചനകള് തിരുത്തുക
48+Comment[mn]=Текст файл боловсруулах
49+Comment[mr]=गद्य फाइल संपादित करा
50+Comment[ms]=Edit fail teks
51+Comment[nb]=Rediger tekstfiler
52+Comment[ne]=पाठ फाइललाई संशोधन गर्नुहोस्
53+Comment[nl]=Tekstbestanden bewerken
54+Comment[nn]=Rediger tekstfiler
55+Comment[no]=Rediger tekstfiler
56+Comment[or]=ପାଠ୍ଯ ଫାଇଲଗୁଡ଼ିକୁ ସମ୍ପାଦନ କରନ୍ତୁ
57+Comment[pa]=ਪਾਠ ਫਾਇਲਾਂ ਸੰਪਾਦਨ
58+Comment[pl]=Edytor plików tekstowych
59+Comment[pt]=Editar ficheiros de texto
60+Comment[pt_BR]=Edite arquivos de texto
61+Comment[ro]=Editare fişiere text
62+Comment[ru]=Редактирование текстовых файлов
63+Comment[sk]=Úprava textových súborov
64+Comment[sl]=Urejanje datotek z besedili
65+Comment[sq]=Përpuno files teksti
66+Comment[sr]=Уређујте текст фајлове
67+Comment[sr@Latn]=Izmeni tekstualne datoteke
68+Comment[sv]=Redigera textfiler
69+Comment[ta]=உரை கோப்புகளை தொகுக்கவும்
70+Comment[th]=แก้ไขแฟ้มข้อความ
71+Comment[tk]=Metin faýllary editle
72+Comment[tr]=Metin dosyaları düzenleyin
73+Comment[uk]=Редактор текстових файлів
74+Comment[vi]=Soạn thảo tập tin văn bản
75+Comment[wa]=Asspougnî des fitchîs tecses
76+Comment[zh_CN]=编辑文本文件
77+Comment[zh_TW]=編輯文字檔
78+TryExec=hx
79+Exec=hx %F
80+Terminal=true
81+Type=Application
82+Keywords=Text;editor;
83+Keywords[ar]=نص;نصوص;محرر;
84+Keywords[fr]=Texte;éditeur;
85+Keywords[ru]=текст;текстовый редактор;
86+Keywords[sr]=Текст;едитор;
87+Keywords[tr]=Metin;düzenleyici;
88+Icon=helix
89+Categories=Utility;TextEditor;
90+StartupNotify=false
91+MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
contrib/completion/hx.bashnew file+23
@@ -0,0 +1,23 @@
1+#!/usr/bin/env bash
2+# Bash completion script for Helix editor
3+
4+_hx() {
5+ # $1 command name
6+ # $2 word being completed
7+ # $3 word preceding
8+ COMPREPLY=()
9+
10+ case "$3" in
11+ -g | --grammar)
12+ COMPREPLY=($(compgen -W "fetch build" -- $2))
13+ ;;
14+ --health)
15+ local languages=$(hx --health |tail -n '+7' |awk '{print $1}' |sed 's/\x1b\[[0-9;]*m//g')
16+ COMPREPLY=($(compgen -W "$languages" -- $2))
17+ ;;
18+ *)
19+ COMPREPLY=($(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- $2))
20+ ;;
21+ esac
22+} && complete -o filenames -F _hx hx
23+
contrib/completion/hx.elvnew file+55
@@ -0,0 +1,55 @@
1+# You can move it here ~/.config/elvish/lib/hx.elv
2+# Or add `eval (slurp < ~/$REPOS/helix/contrib/completion/hx.elv)`
3+# Be sure to replace `$REPOS` with something that makes sense for you!
4+
5+### Renders a pretty completion candidate
6+var candidate = { | _stem _desc |
7+ edit:complex-candidate $_stem &display=(styled $_stem bold)(styled " "$_desc dim)
8+}
9+
10+### These commands will invalidate further input (i.e. not react to them)
11+var skips = [ "--tutor" "--help" "--version" "-V" "--health" ]
12+
13+### Grammar commands
14+var grammar = [ "--grammar" "-g" ]
15+
16+### Config commands
17+var config = [ "--config" "-c" ]
18+
19+### Set an arg-completer for the `hx` binary
20+set edit:completion:arg-completer[hx] = {|@args|
21+ var n = (count $args)
22+ if (>= $n 3) {
23+ # Stop completions if passed arg will take presedence
24+ # and invalidate further input
25+ if (has-value $skips $args[-2]) {
26+ return
27+ }
28+ # If the previous arg == --grammar, then only suggest:
29+ if (has-value $grammar $args[-2]) {
30+ $candidate "fetch" "Fetch the tree-sitter grammars"
31+ $candidate "build" "Build the tree-sitter grammars"
32+ return
33+ }
34+ # When we have --config, we need a file
35+ if (has-values $config $args[-2]) {
36+ edit:complete-filename $args[-1] | each { |v| put $v[stem] }
37+ return
38+ }
39+ # When we have --log, we need a file
40+ if (has-values "log" $args[-2]) {
41+ edit:complete-filename $args[-1] | each { |v| put $v[stem] }
42+ return
43+ }
44+ }
45+ edit:complete-filename $args[-1] | each { |v| put $v[stem]}
46+ $candidate "--help" "(Prints help information)"
47+ $candidate "--version" "(Prints version information)"
48+ $candidate "--tutor" "(Loads the tutorial)"
49+ $candidate "--health" "(Checks for errors in editor setup)"
50+ $candidate "--grammar" "(Fetch or build the tree-sitter grammars)"
51+ $candidate "--vsplit" "(Splits all given files vertically)"
52+ $candidate "--hsplit" "(Splits all given files horizontally)"
53+ $candidate "--config" "(Specifies a file to use for configuration)"
54+ $candidate "--log" "(Specifies a file to write log data into)"
55+}
contrib/completion/hx.fishnew file+15
@@ -0,0 +1,15 @@
1+#!/usr/bin/env fish
2+# Fish completion script for Helix editor
3+
4+set -l langs (hx --health |tail -n '+7' |awk '{print $1}' |sed 's/\x1b\[[0-9;]*m//g')
5+
6+complete -c hx -s h -l help -d "Prints help information"
7+complete -c hx -l tutor -d "Loads the tutorial"
8+complete -c hx -l health -x -a "$langs" -d "Checks for errors in editor setup"
9+complete -c hx -s g -l grammar -x -a "fetch build" -d "Fetches or builds tree-sitter grammars"
10+complete -c hx -s v -o vv -o vvv -d "Increases logging verbosity"
11+complete -c hx -s V -l version -d "Prints version information"
12+complete -c hx -l vsplit -d "Splits all given files vertically into different windows"
13+complete -c hx -l hsplit -d "Splits all given files horizontally into different windows"
14+complete -c hx -s c -l config -r -d "Specifies a file to use for completion"
15+complete -c hx -l log -r -d "Specifies a file to write log data into"
contrib/completion/hx.zshnew file+34
@@ -0,0 +1,34 @@
1+#compdef _hx hx
2+# Zsh completion script for Helix editor
3+
4+_hx() {
5+ _arguments -C \
6+ "-h[Prints help information]" \
7+ "--help[Prints help information]" \
8+ "-v[Increase logging verbosity]" \
9+ "-vv[Increase logging verbosity]" \
10+ "-vvv[Increase logging verbosity]" \
11+ "-V[Prints version information]" \
12+ "--version[Prints version information]" \
13+ "--tutor[Loads the tutorial]" \
14+ "--health[Checks for errors in editor setup]:language:->health" \
15+ "-g[Fetches or builds tree-sitter grammars]:action:->grammar" \
16+ "--grammar[Fetches or builds tree-sitter grammars]:action:->grammar" \
17+ "--vsplit[Splits all given files vertically into different windows]" \
18+ "--hsplit[Splits all given files horizontally into different windows]" \
19+ "-c[Specifies a file to use for configuration]" \
20+ "--config[Specifies a file to use for configuration]" \
21+ "--log[Specifies a file to write log data into]" \
22+ "*:file:_files"
23+
24+ case "$state" in
25+ health)
26+ local languages=($(hx --health |tail -n '+7' |awk '{print $1}' |sed 's/\x1b\[[0-9;]*m//g'))
27+ _values 'language' $languages
28+ ;;
29+ grammar)
30+ _values 'action' fetch build
31+ ;;
32+ esac
33+}
34+
contrib/helix.pngnew file

Binary file not shown.

contrib/themesnew file+1
@@ -0,0 +1 @@
1+../runtime/themes
default.nixnew file+8
@@ -0,0 +1,8 @@
1+# Flake's default package for non-flake-enabled nix instances
2+let
3+ compat = builtins.fetchTarball {
4+ url = "https://github.com/edolstra/flake-compat/archive/b4a34015c698c7793d592d66adbab377907a2be8.tar.gz";
5+ sha256 = "sha256:1qc703yg0babixi6wshn5wm2kgl5y1drcswgszh4xxzbrwkk9sv7";
6+ };
7+in
8+ (import compat {src = ./.;}).defaultNix.default
docs/CONTRIBUTING.mdnew file+68
@@ -0,0 +1,68 @@
1+# Contributing
2+
3+Contributors are very welcome! **No contribution is too small and all contributions are valued.**
4+
5+Some suggestions to get started:
6+
7+- You can look at the [good first issue][good-first-issue] label on the issue tracker.
8+- Help with packaging on various distributions needed!
9+- To use print debugging to the [Helix log file][log-file], you must:
10+ * Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`)
11+ * Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher verbosity)
12+ * Want to display the logs in a separate file instead of using the `:log-open` command in your compiled Helix editor? Start your debug version with `cargo run -- --log foo.log` and in a new terminal use `tail -f foo.log`
13+- Instead of running a release version of Helix, while developing you may want to run in debug mode with `cargo run` which is way faster to compile
14+- Looking for even faster compile times? Give a try to [mold](https://github.com/rui314/mold)
15+- If your preferred language is missing, integrating a tree-sitter grammar for
16+ it and defining syntax highlight queries for it is straight forward and
17+ doesn't require much knowledge of the internals.
18+
19+We provide an [architecture.md][architecture.md] that should give you
20+a good overview of the internals.
21+
22+# Auto generated documentation
23+
24+Some parts of [the book][docs] are autogenerated from the code itself,
25+like the list of `:commands` and supported languages. To generate these
26+files, run
27+
28+```shell
29+cargo xtask docgen
30+```
31+
32+inside the project. We use [xtask][xtask] as an ad-hoc task runner.
33+
34+To preview the book itself, install [mdbook][mdbook]. Then, run
35+
36+```shell
37+mdbook serve book
38+```
39+
40+and visit [http://localhost:3000](http://localhost:3000).
41+
42+# Testing
43+
44+## Unit tests/Documentation tests
45+
46+Run `cargo test --workspace` to run unit tests and documentation tests in all packages.
47+
48+## Integration tests
49+
50+Integration tests for helix-term can be run with `cargo integration-test`. Code
51+contributors are strongly encouraged to write integration tests for their code.
52+Existing tests can be used as examples. Helpers can be found in
53+[helpers.rs][helpers.rs]. The log level can be set with the `HELIX_LOG_LEVEL`
54+environment variable, e.g. `HELIX_LOG_LEVEL=debug cargo integration-test`.
55+
56+## Minimum Stable Rust Version (MSRV) Policy
57+
58+Helix follows the MSRV of Firefox.
59+The current MSRV and future changes to the MSRV are listed in the [Firefox documentation].
60+
61+[Firefox documentation]: https://firefox-source-docs.mozilla.org/writing-rust-code/update-policy.html
62+[good-first-issue]: https://github.com/helix-editor/helix/labels/E-easy
63+[log-file]: https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file
64+[architecture.md]: ./architecture.md
65+[docs]: https://docs.helix-editor.com/
66+[xtask]: https://github.com/matklad/cargo-xtask
67+[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
68+[helpers.rs]: ../helix-term/tests/test/helpers.rs
docs/architecture.mdnew file+111
@@ -0,0 +1,111 @@
1+
2+| Crate | Description |
3+| ----------- | ----------- |
4+| helix-core | Core editing primitives, functional. |
5+| helix-lsp | Language server client |
6+| helix-dap | Debug Adapter Protocol (DAP) client |
7+| helix-loader | Functions for building, fetching, and loading external resources |
8+| helix-view | UI abstractions for use in backends, imperative shell. |
9+| helix-term | Terminal UI |
10+| helix-tui | TUI primitives, forked from tui-rs, inspired by Cursive |
11+
12+
13+This document contains a high-level overview of Helix internals.
14+
15+> NOTE: Use `cargo doc --open` for API documentation as well as dependency
16+> documentation.
17+
18+## Core
19+
20+The core contains basic building blocks used to construct the editor. It is
21+heavily based on [CodeMirror 6](https://codemirror.net/6/docs/). The primitives
22+are functional: most operations won't modify data in place but instead return
23+a new copy.
24+
25+The main data structure used for representing buffers is a `Rope`. We re-export
26+the excellent [ropey](https://github.com/cessen/ropey) library. Ropes are cheap
27+to clone, and allow us to easily make snapshots of a text state.
28+
29+Multiple selections are a core editing primitive. Document selections are
30+represented by a `Selection`. Each `Range` in the selection consists of a moving
31+`head` and an immovable `anchor`. A single cursor in the editor is simply
32+a selection with a single range, with the head and the anchor in the same
33+position.
34+
35+Ropes are modified by constructing an OT-like `Transaction`. It represents
36+a single coherent change to the document and can be applied to the rope.
37+A transaction can be inverted to produce an undo. Selections and marks can be
38+mapped over a transaction to translate to a position in the new text state after
39+applying the transaction.
40+
41+> NOTE: `Transaction::change`/`Transaction::change_by_selection` is the main
42+> interface used to generate text edits.
43+
44+`Syntax` is the interface used to interact with tree-sitter ASTs for syntax
45+highlighting and other features.
46+
47+## View
48+
49+The `view` layer was supposed to be a frontend-agnostic imperative library that
50+would build on top of `core` to provide the common editor logic. Currently it's
51+tied to the terminal UI.
52+
53+A `Document` ties together the `Rope`, `Selection`(s), `Syntax`, document
54+`History`, language server (etc.) into a comprehensive representation of an open
55+file.
56+
57+A `View` represents an open split in the UI. It holds the currently open
58+document ID and other related state. Views encapsulate the gutter, status line,
59+diagnostics, and the inner area where the code is displayed.
60+
61+> NOTE: Multiple views are able to display the same document, so the document
62+> contains selections for each view. To retrieve, `document.selection()` takes
63+> a `ViewId`.
64+
65+`Info` is the autoinfo box that shows hints when awaiting another key with bindings
66+like `g` and `m`. It is attached to the viewport as a whole.
67+
68+`Surface` is like a buffer to which widgets draw themselves to, and the
69+surface is then rendered on the screen on each cycle.
70+
71+`Rect`s are areas (simply an x and y coordinate with the origin at the
72+screen top left and then a height and width) which are part of a
73+`Surface`. They can be used to limit the area to which a `Component` can
74+render. For example if we wrap a `Markdown` component in a `Popup`
75+(think the documentation popup with space+k), Markdown's render method
76+will get a Rect that is the exact size of the popup.
77+
78+Widgets are called `Component`s internally, and you can see most of them
79+in `helix-term/src/ui`. Some components like `Popup` and `Overlay` can take
80+other components as children.
81+
82+`Layer`s are how multiple components are displayed, and is simply a
83+`Vec<Component>`. Layers are managed by the `Compositor`. On each top
84+level render call, the compositor renders each component in the order
85+they were pushed into the stack. This makes multiple components "layer"
86+on top of one another. Hence we get a file picker displayed over the
87+editor, etc.
88+
89+The `Editor` holds the global state: all the open documents, a tree
90+representation of all the view splits, the configuration, and a registry of
91+language servers. To open or close files, interact with the editor.
92+
93+## LSP
94+
95+A language server protocol client.
96+
97+## Term
98+
99+The terminal frontend.
100+
101+The `main` function sets up a new `Application` that runs the event loop.
102+
103+`commands.rs` is probably the most interesting file. It contains all commands
104+(actions tied to keybindings).
105+
106+`keymap.rs` links commands to key combinations.
107+
108+
109+## TUI / Term
110+
111+TODO: document Component and rendering related stuff
docs/releases.mdnew file+67
@@ -0,0 +1,67 @@
1+## Checklist
2+
3+Helix releases are versioned in the Calendar Versioning scheme:
4+`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
5+`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
6+being published.
7+
8+* Merge the changelog PR
9+* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
10+* Tag and push
11+ * `git tag -s -m "<tag>" -a <tag> && git push`
12+ * Make sure to switch to master and pull first
13+* Edit the `Cargo.toml` file and change the date in the `version` field to the next planned release
14+ * Due to Cargo having a strict requirement on SemVer with 3 or more version
15+ numbers, a `0` is required in the micro version; however, unless we are
16+ publishing a patch release after a major release, the `.0` is dropped in
17+ the user facing version.
18+ * Releases are planned to happen every two months, so `22.05.0` would change to `22.07.0`
19+ * If we are pushing a patch/bugfix release in the same month as the previous
20+ release, bump the micro version, e.g. `22.07.0` to `22.07.1`
21+* Wait for the Release CI to finish
22+ * It will automatically turn the git tag into a GitHub release when it uploads artifacts
23+* Edit the new release
24+ * Use `<tag>` as the title
25+ * Link to the changelog and release notes
26+* Merge the release notes PR
27+* Download the macos and linux binaries and update the `sha256`s in the [homebrew formula]
28+ * Use `sha256sum` on the downloaded `.tar.xz` files to determine the hash
29+* Link to the release notes in this-week-in-rust
30+ * [Example PR](https://github.com/rust-lang/this-week-in-rust/pull/3300)
31+* Post to reddit
32+ * [Example post](https://www.reddit.com/r/rust/comments/uzp5ze/helix_editor_2205_released/)
33+
34+[homebrew formula]: https://github.com/Homebrew/homebrew-core/blob/master/Formula/helix.rb
35+
36+## Changelog Curation
37+
38+The changelog is currently created manually by reading through commits in the
39+log since the last release. GitHub's compare view is a nice way to approach
40+this. For example, when creating the 22.07 release notes, this compare link
41+may be used
42+
43+```
44+https://github.com/helix-editor/helix/compare/22.05...master
45+```
46+
47+Either side of the triple-dot may be replaced with an exact revision, so if
48+you wish to incrementally compile the changelog, you can tackle a weeks worth
49+or so, record the revision where you stopped, and use that as a starting point
50+next week:
51+
52+```
53+https://github.com/helix-editor/helix/compare/7706a4a0d8b67b943c31d0c5f7b00d357b5d838d...master
54+```
55+
56+A work-in-progress commit for a changelog might look like
57+[this example](https://github.com/helix-editor/helix/commit/831adfd4c709ca16b248799bfef19698d5175e55).
58+
59+Not every PR or commit needs a blurb in the changelog. Each release section
60+tends to have a blurb that links to a GitHub comparison between release
61+versions for convenience:
62+
63+> As usual, the following is a summary of each of the changes since the last
64+> release. For the full log, check out the git log.
65+
66+Typically, small changes like dependencies or documentation updates, refactors,
67+or meta changes like GitHub Actions work are left out.
docs/vision.mdnew file+24
@@ -0,0 +1,24 @@
1+The Helix project still has a ways to go before reaching its goals. This document outlines some of those goals and the overall vision for the project.
2+
3+# Vision
4+
5+An efficient, batteries-included editor you can take anywhere and be productive... if it's your kind of thing.
6+
7+* **Cross-platform.** Whether on Linux, Windows, or OSX, you should be able to take your editor with you.
8+* **Terminal first.** Not all environments have a windowing system, and you shouldn't have to abandon your preferred editor in those cases.
9+* **Native.** No Electron or HTML DOM here. We want an efficient, native-compiled editor that can run with minimal resources when needed. If you're working on a Raspberry Pi, your editor shouldn't consume half of your RAM.
10+* **Batteries included.** Both the default configuration and bundled features should be enough to have a good editing experience and be productive. You shouldn't need a massive custom config or external executables and plugins for basic features and functionality.
11+* **Don't try to be everything for everyone.** There are many great editors out there to choose from. Let's make Helix *one of* those great options, with its own take on things.
12+
13+# Goals
14+
15+Vision statements are all well and good, but are also vague and subjective. Here is a (non-exhaustive) list of some of Helix's more concrete goals, to help give a clearer idea of the project's direction:
16+
17+* **Modal.** Vim is a great idea.
18+* **Selection -> Action**, not Verb -> Object. Interaction models aren't linguistics, and "selection first" lets you see what you're doing (among other benefits).
19+* **We aren't playing code golf.** It's more important for the keymap to be consistent and easy to memorize than it is to save a key stroke or two when editing.
20+* **Built-in tools** for working with code bases efficiently. Most projects aren't a single file, and an editor should handle that as a first-class use case. In Helix's case, this means (among other things) a fuzzy-search file navigator and LSP support.
21+* **Edit anything** that comes up when coding, within reason. Whether it's a 200 MB XML file, a megabyte of minified javascript on a single line, or Japanese text encoded in ShiftJIS, you should be able to open it and edit it without problems. (Note: this doesn't mean handle every esoteric use case. Sometimes you do just need a specialized tool, and Helix isn't that.)
22+* **Configurable**, within reason. Although the defaults should be good, not everyone will agree on what "good" is. Within the bounds of Helix's core interaction models, it should be reasonably configurable so that it can be "good" for more people. This means, for example, custom key maps among other things.
23+* **Extensible**, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed.
24+* **Clean code base.** Sometimes other factors (e.g. significant performance gains, important features, correctness, etc.) will trump strict readability, but we nevertheless want to keep the code base straightforward and easy to understand to the extent we can.
flake.locknew file+106
@@ -0,0 +1,106 @@
1+{
2+ "nodes": {
3+ "crane": {
4+ "inputs": {
5+ "nixpkgs": [
6+ "nixpkgs"
7+ ]
8+ },
9+ "locked": {
10+ "lastModified": 1701025348,
11+ "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
12+ "owner": "ipetkov",
13+ "repo": "crane",
14+ "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
15+ "type": "github"
16+ },
17+ "original": {
18+ "owner": "ipetkov",
19+ "repo": "crane",
20+ "type": "github"
21+ }
22+ },
23+ "flake-utils": {
24+ "inputs": {
25+ "systems": "systems"
26+ },
27+ "locked": {
28+ "lastModified": 1694529238,
29+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
30+ "owner": "numtide",
31+ "repo": "flake-utils",
32+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
33+ "type": "github"
34+ },
35+ "original": {
36+ "owner": "numtide",
37+ "repo": "flake-utils",
38+ "type": "github"
39+ }
40+ },
41+ "nixpkgs": {
42+ "locked": {
43+ "lastModified": 1700794826,
44+ "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
45+ "owner": "nixos",
46+ "repo": "nixpkgs",
47+ "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
48+ "type": "github"
49+ },
50+ "original": {
51+ "owner": "nixos",
52+ "ref": "nixos-unstable",
53+ "repo": "nixpkgs",
54+ "type": "github"
55+ }
56+ },
57+ "root": {
58+ "inputs": {
59+ "crane": "crane",
60+ "flake-utils": "flake-utils",
61+ "nixpkgs": "nixpkgs",
62+ "rust-overlay": "rust-overlay"
63+ }
64+ },
65+ "rust-overlay": {
66+ "inputs": {
67+ "flake-utils": [
68+ "flake-utils"
69+ ],
70+ "nixpkgs": [
71+ "nixpkgs"
72+ ]
73+ },
74+ "locked": {
75+ "lastModified": 1701137803,
76+ "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
77+ "owner": "oxalica",
78+ "repo": "rust-overlay",
79+ "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
80+ "type": "github"
81+ },
82+ "original": {
83+ "owner": "oxalica",
84+ "repo": "rust-overlay",
85+ "type": "github"
86+ }
87+ },
88+ "systems": {
89+ "locked": {
90+ "lastModified": 1681028828,
91+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
92+ "owner": "nix-systems",
93+ "repo": "default",
94+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
95+ "type": "github"
96+ },
97+ "original": {
98+ "owner": "nix-systems",
99+ "repo": "default",
100+ "type": "github"
101+ }
102+ }
103+ },
104+ "root": "root",
105+ "version": 7
106+}
flake.nixnew file+200
@@ -0,0 +1,200 @@
1+{
2+ description = "A post-modern text editor.";
3+
4+ inputs = {
5+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
6+ flake-utils.url = "github:numtide/flake-utils";
7+ rust-overlay = {
8+ url = "github:oxalica/rust-overlay";
9+ inputs = {
10+ nixpkgs.follows = "nixpkgs";
11+ flake-utils.follows = "flake-utils";
12+ };
13+ };
14+ crane = {
15+ url = "github:ipetkov/crane";
16+ inputs.nixpkgs.follows = "nixpkgs";
17+ };
18+ };
19+
20+ outputs = {
21+ self,
22+ nixpkgs,
23+ crane,
24+ flake-utils,
25+ rust-overlay,
26+ ...
27+ }:
28+ flake-utils.lib.eachDefaultSystem (system: let
29+ pkgs = import nixpkgs {
30+ inherit system;
31+ overlays = [(import rust-overlay)];
32+ };
33+ mkRootPath = rel:
34+ builtins.path {
35+ path = "${toString ./.}/${rel}";
36+ name = rel;
37+ };
38+ filteredSource = let
39+ pathsToIgnore = [
40+ ".envrc"
41+ ".ignore"
42+ ".github"
43+ ".gitignore"
44+ "logo_dark.svg"
45+ "logo_light.svg"
46+ "rust-toolchain.toml"
47+ "rustfmt.toml"
48+ "runtime"
49+ "screenshot.png"
50+ "book"
51+ "docs"
52+ "README.md"
53+ "CHANGELOG.md"
54+ "shell.nix"
55+ "default.nix"
56+ "grammars.nix"
57+ "flake.nix"
58+ "flake.lock"
59+ ];
60+ ignorePaths = path: type: let
61+ inherit (nixpkgs) lib;
62+ # split the nix store path into its components
63+ components = lib.splitString "/" path;
64+ # drop off the `/nix/hash-source` section from the path
65+ relPathComponents = lib.drop 4 components;
66+ # reassemble the path components
67+ relPath = lib.concatStringsSep "/" relPathComponents;
68+ in
69+ lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
70+ in
71+ builtins.path {
72+ name = "helix-source";
73+ path = toString ./.;
74+ # filter out unnecessary paths
75+ filter = ignorePaths;
76+ };
77+ makeOverridableHelix = old: config: let
78+ grammars = pkgs.callPackage ./grammars.nix config;
79+ runtimeDir = pkgs.runCommand "helix-runtime" {} ''
80+ mkdir -p $out
81+ ln -s ${mkRootPath "runtime"}/* $out
82+ rm -r $out/grammars
83+ ln -s ${grammars} $out/grammars
84+ '';
85+ helix-wrapped =
86+ pkgs.runCommand
87+ old.name
88+ {
89+ inherit (old) pname version;
90+ meta = old.meta or {};
91+ passthru =
92+ (old.passthru or {})
93+ // {
94+ unwrapped = old;
95+ };
96+ nativeBuildInputs = [pkgs.makeWrapper];
97+ makeWrapperArgs = config.makeWrapperArgs or [];
98+ }
99+ ''
100+ cp -rs --no-preserve=mode,ownership ${old} $out
101+ wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
102+ '';
103+ in
104+ helix-wrapped
105+ // {
106+ override = makeOverridableHelix old;
107+ passthru =
108+ helix-wrapped.passthru
109+ // {
110+ wrapper = old: makeOverridableHelix old config;
111+ };
112+ };
113+ stdenv =
114+ if pkgs.stdenv.isLinux
115+ then pkgs.stdenv
116+ else pkgs.clangStdenv;
117+ rustFlagsEnv =
118+ if stdenv.isLinux
119+ then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
120+ else "$RUSTFLAGS";
121+ rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
122+ craneLibMSRV = (crane.mkLib pkgs).overrideToolchain rustToolchain;
123+ craneLibStable = (crane.mkLib pkgs).overrideToolchain pkgs.pkgsBuildHost.rust-bin.stable.latest.default;
124+ commonArgs = {
125+ inherit stdenv;
126+ inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;}) pname;
127+ inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./Cargo.toml;}) version;
128+ src = filteredSource;
129+ # disable fetching and building of tree-sitter grammars in the helix-term build.rs
130+ HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
131+ buildInputs = [stdenv.cc.cc.lib];
132+ # disable tests
133+ doCheck = false;
134+ meta.mainProgram = "hx";
135+ };
136+ cargoArtifacts = craneLibMSRV.buildDepsOnly commonArgs;
137+ in {
138+ packages = {
139+ helix-unwrapped = craneLibStable.buildPackage (commonArgs
140+ // {
141+ cargoArtifacts = craneLibStable.buildDepsOnly commonArgs;
142+ postInstall = ''
143+ mkdir -p $out/share/applications $out/share/icons/hicolor/scalable/apps $out/share/icons/hicolor/256x256/apps
144+ cp contrib/Helix.desktop $out/share/applications
145+ cp logo.svg $out/share/icons/hicolor/scalable/apps/helix.svg
146+ cp contrib/helix.png $out/share/icons/hicolor/256x256/apps
147+ '';
148+ });
149+ helix = makeOverridableHelix self.packages.${system}.helix-unwrapped {};
150+ default = self.packages.${system}.helix;
151+ };
152+
153+ checks = {
154+ # Build the crate itself
155+ inherit (self.packages.${system}) helix;
156+
157+ clippy = craneLibMSRV.cargoClippy (commonArgs
158+ // {
159+ inherit cargoArtifacts;
160+ cargoClippyExtraArgs = "--all-targets -- --deny warnings";
161+ });
162+
163+ fmt = craneLibMSRV.cargoFmt commonArgs;
164+
165+ doc = craneLibMSRV.cargoDoc (commonArgs
166+ // {
167+ inherit cargoArtifacts;
168+ });
169+
170+ test = craneLibMSRV.cargoTest (commonArgs
171+ // {
172+ inherit cargoArtifacts;
173+ });
174+ };
175+
176+ devShells.default = pkgs.mkShell {
177+ inputsFrom = builtins.attrValues self.checks.${system};
178+ nativeBuildInputs = with pkgs;
179+ [lld_13 cargo-flamegraph rust-analyzer]
180+ ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
181+ ++ (lib.optional stdenv.isLinux pkgs.lldb)
182+ ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
183+ shellHook = ''
184+ export HELIX_RUNTIME="$PWD/runtime"
185+ export RUST_BACKTRACE="1"
186+ export RUSTFLAGS="${rustFlagsEnv}"
187+ '';
188+ };
189+ })
190+ // {
191+ overlays.default = final: prev: {
192+ inherit (self.packages.${final.system}) helix;
193+ };
194+ };
195+
196+ nixConfig = {
197+ extra-substituters = ["https://helix.cachix.org"];
198+ extra-trusted-public-keys = ["helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs="];
199+ };
200+}
grammars.nixnew file+124
@@ -0,0 +1,124 @@
1+{
2+ stdenv,
3+ lib,
4+ runCommandLocal,
5+ runCommand,
6+ yj,
7+ includeGrammarIf ? _: true,
8+ grammarOverlays ? [],
9+ ...
10+}: let
11+ # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
12+ # before parsing
13+ languages-json = runCommandLocal "languages-toml-to-json" {} ''
14+ ${yj}/bin/yj -t < ${./languages.toml} > $out
15+ '';
16+ languagesConfig =
17+ if lib.versionAtLeast builtins.nixVersion "2.6.0"
18+ then builtins.fromTOML (builtins.readFile ./languages.toml)
19+ else builtins.fromJSON (builtins.readFile (builtins.toPath languages-json));
20+ isGitGrammar = grammar:
21+ builtins.hasAttr "source" grammar
22+ && builtins.hasAttr "git" grammar.source
23+ && builtins.hasAttr "rev" grammar.source;
24+ isGitHubGrammar = grammar: lib.hasPrefix "https://github.com" grammar.source.git;
25+ toGitHubFetcher = url: let
26+ match = builtins.match "https://github\.com/([^/]*)/([^/]*)/?" url;
27+ in {
28+ owner = builtins.elemAt match 0;
29+ repo = builtins.elemAt match 1;
30+ };
31+ gitGrammars = builtins.filter isGitGrammar languagesConfig.grammar;
32+ buildGrammar = grammar: let
33+ gh = toGitHubFetcher grammar.source.git;
34+ sourceGit = builtins.fetchTree {
35+ type = "git";
36+ url = grammar.source.git;
37+ rev = grammar.source.rev;
38+ ref = grammar.source.ref or "HEAD";
39+ shallow = true;
40+ };
41+ sourceGitHub = builtins.fetchTree {
42+ type = "github";
43+ owner = gh.owner;
44+ repo = gh.repo;
45+ inherit (grammar.source) rev;
46+ };
47+ source =
48+ if isGitHubGrammar grammar
49+ then sourceGitHub
50+ else sourceGit;
51+ in
52+ stdenv.mkDerivation {
53+ # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
54+
55+ pname = "helix-tree-sitter-${grammar.name}";
56+ version = grammar.source.rev;
57+
58+ src = source;
59+ sourceRoot = if builtins.hasAttr "subpath" grammar.source then
60+ "source/${grammar.source.subpath}"
61+ else
62+ "source";
63+
64+ dontConfigure = true;
65+
66+ FLAGS = [
67+ "-Isrc"
68+ "-g"
69+ "-O3"
70+ "-fPIC"
71+ "-fno-exceptions"
72+ "-Wl,-z,relro,-z,now"
73+ ];
74+
75+ NAME = grammar.name;
76+
77+ buildPhase = ''
78+ runHook preBuild
79+
80+ if [[ -e src/scanner.cc ]]; then
81+ $CXX -c src/scanner.cc -o scanner.o $FLAGS
82+ elif [[ -e src/scanner.c ]]; then
83+ $CC -c src/scanner.c -o scanner.o $FLAGS
84+ fi
85+
86+ $CC -c src/parser.c -o parser.o $FLAGS
87+ $CXX -shared -o $NAME.so *.o
88+
89+ ls -al
90+
91+ runHook postBuild
92+ '';
93+
94+ installPhase = ''
95+ runHook preInstall
96+ mkdir $out
97+ mv $NAME.so $out/
98+ runHook postInstall
99+ '';
100+
101+ # Strip failed on darwin: strip: error: symbols referenced by indirect symbol table entries that can't be stripped
102+ fixupPhase = lib.optionalString stdenv.isLinux ''
103+ runHook preFixup
104+ $STRIP $out/$NAME.so
105+ runHook postFixup
106+ '';
107+ };
108+ grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
109+ builtGrammars = builtins.map (grammar: {
110+ inherit (grammar) name;
111+ value = buildGrammar grammar;
112+ }) grammarsToBuild;
113+ extensibleGrammars =
114+ lib.makeExtensible (self: builtins.listToAttrs builtGrammars);
115+ overlayedGrammars = lib.pipe extensibleGrammars
116+ (builtins.map (overlay: grammar: grammar.extend overlay) grammarOverlays);
117+ grammarLinks = lib.mapAttrsToList
118+ (name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
119+ (lib.filterAttrs (n: v: lib.isDerivation v) overlayedGrammars);
120+in
121+ runCommand "consolidated-helix-grammars" {} ''
122+ mkdir -p $out
123+ ${builtins.concatStringsSep "\n" grammarLinks}
124+ ''
helix-core/Cargo.toml+48−12
@@ -1,21 +1,57 @@
1[package]
1[package]
2name = "helix-core"
2name = "helix-core"
3−version = "0.1.0"
3+description = "Helix editor core editing primitives"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
4+include = ["src/**/*", "README.md"]
5+version.workspace = true
6+authors.workspace = true
5−edition = "2018"
7+edition.workspace = true
8+license.workspace = true
9+rust-version.workspace = true
10+categories.workspace = true
11+repository.workspace = true
12+homepage.workspace = true
6
13
7−# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14+[features]
15+unicode-lines = ["ropey/unicode_lines"]
16+integration = []
8
17
9[dependencies]
18[dependencies]
10−helix-syntax = { path = "../helix-syntax" }
19+helix-loader = { path = "../helix-loader" }
11
20
12−ropey = "1.2"
21+ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
13−anyhow = "1"
22+smallvec = "1.11"
14−smallvec = "1.4"
23+smartstring = "1.0.1"
15−tendril = { git = "https://github.com/servo/tendril" }
16−unicode-segmentation = "1.6"
24+unicode-segmentation = "1.10"
17unicode-width = "0.1"
25unicode-width = "0.1"
26+unicode-general-category = "0.6"
18# slab = "0.4.2"
27# slab = "0.4.2"
28+slotmap = "1.0"
19−tree-sitter = "0.17"
29+tree-sitter.workspace = true
20−once_cell = "1.4"
30+once_cell = "1.19"
31+arc-swap = "1"
21regex = "1"
32regex = "1"
33+bitflags = "2.4"
34+ahash = "0.8.6"
35+hashbrown = { version = "0.14.3", features = ["raw"] }
36+dunce = "1.0"
37+
38+log = "0.4"
39+serde = { version = "1.0", features = ["derive"] }
40+serde_json = "1.0"
41+toml = "0.7"
42+
43+imara-diff = "0.1.0"
44+
45+encoding_rs = "0.8"
46+
47+chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
48+
49+etcetera = "0.8"
50+textwrap = "0.16.0"
51+
52+nucleo.workspace = true
53+parking_lot = "0.12"
54+
55+[dev-dependencies]
56+quickcheck = { version = "1", default-features = false }
57+indoc = "2.0.4"
helix-core/src/auto_pairs.rsnew file+373
@@ -0,0 +1,373 @@
1+//! When typing the opening character of one of the possible pairs defined below,
2+//! this module provides the functionality to insert the paired closing character.
3+
4+use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
5+use std::collections::HashMap;
6+
7+use smallvec::SmallVec;
8+
9+// Heavily based on https://github.com/codemirror/closebrackets/
10+pub const DEFAULT_PAIRS: &[(char, char)] = &[
11+ ('(', ')'),
12+ ('{', '}'),
13+ ('[', ']'),
14+ ('\'', '\''),
15+ ('"', '"'),
16+ ('`', '`'),
17+];
18+
19+/// The type that represents the collection of auto pairs,
20+/// keyed by both opener and closer.
21+#[derive(Debug, Clone)]
22+pub struct AutoPairs(HashMap<char, Pair>);
23+
24+/// Represents the config for a particular pairing.
25+#[derive(Debug, Clone, Copy)]
26+pub struct Pair {
27+ pub open: char,
28+ pub close: char,
29+}
30+
31+impl Pair {
32+ /// true if open == close
33+ pub fn same(&self) -> bool {
34+ self.open == self.close
35+ }
36+
37+ /// true if all of the pair's conditions hold for the given document and range
38+ pub fn should_close(&self, doc: &Rope, range: &Range) -> bool {
39+ let mut should_close = Self::next_is_not_alpha(doc, range);
40+
41+ if self.same() {
42+ should_close &= Self::prev_is_not_alpha(doc, range);
43+ }
44+
45+ should_close
46+ }
47+
48+ pub fn next_is_not_alpha(doc: &Rope, range: &Range) -> bool {
49+ let cursor = range.cursor(doc.slice(..));
50+ let next_char = doc.get_char(cursor);
51+ next_char.map(|c| !c.is_alphanumeric()).unwrap_or(true)
52+ }
53+
54+ pub fn prev_is_not_alpha(doc: &Rope, range: &Range) -> bool {
55+ let cursor = range.cursor(doc.slice(..));
56+ let prev_char = prev_char(doc, cursor);
57+ prev_char.map(|c| !c.is_alphanumeric()).unwrap_or(true)
58+ }
59+}
60+
61+impl From<&(char, char)> for Pair {
62+ fn from(&(open, close): &(char, char)) -> Self {
63+ Self { open, close }
64+ }
65+}
66+
67+impl From<(&char, &char)> for Pair {
68+ fn from((open, close): (&char, &char)) -> Self {
69+ Self {
70+ open: *open,
71+ close: *close,
72+ }
73+ }
74+}
75+
76+impl AutoPairs {
77+ /// Make a new AutoPairs set with the given pairs and default conditions.
78+ pub fn new<'a, V: 'a, A>(pairs: V) -> Self
79+ where
80+ V: IntoIterator<Item = A>,
81+ A: Into<Pair>,
82+ {
83+ let mut auto_pairs = HashMap::new();
84+
85+ for pair in pairs.into_iter() {
86+ let auto_pair = pair.into();
87+
88+ auto_pairs.insert(auto_pair.open, auto_pair);
89+
90+ if auto_pair.open != auto_pair.close {
91+ auto_pairs.insert(auto_pair.close, auto_pair);
92+ }
93+ }
94+
95+ Self(auto_pairs)
96+ }
97+
98+ pub fn get(&self, ch: char) -> Option<&Pair> {
99+ self.0.get(&ch)
100+ }
101+}
102+
103+impl Default for AutoPairs {
104+ fn default() -> Self {
105+ AutoPairs::new(DEFAULT_PAIRS.iter())
106+ }
107+}
108+
109+// insert hook:
110+// Fn(doc, selection, char) => Option<Transaction>
111+// problem is, we want to do this per range, so we can call default handler for some ranges
112+// so maybe ret Vec<Option<Change>>
113+// but we also need to be able to return transactions...
114+//
115+// to simplify, maybe return Option<Transaction> and just reimplement the default
116+
117+// [TODO]
118+// * delete implementation where it erases the whole bracket (|) -> |
119+// * change to multi character pairs to handle cases like placing the cursor in the
120+// middle of triple quotes, and more exotic pairs like Jinja's {% %}
121+
122+#[must_use]
123+pub fn hook(doc: &Rope, selection: &Selection, ch: char, pairs: &AutoPairs) -> Option<Transaction> {
124+ log::trace!("autopairs hook selection: {:#?}", selection);
125+
126+ if let Some(pair) = pairs.get(ch) {
127+ if pair.same() {
128+ return Some(handle_same(doc, selection, pair));
129+ } else if pair.open == ch {
130+ return Some(handle_open(doc, selection, pair));
131+ } else if pair.close == ch {
132+ // && char_at pos == close
133+ return Some(handle_close(doc, selection, pair));
134+ }
135+ }
136+
137+ None
138+}
139+
140+fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
141+ if pos == 0 {
142+ return None;
143+ }
144+
145+ doc.get_char(pos - 1)
146+}
147+
148+/// calculate what the resulting range should be for an auto pair insertion
149+fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted: usize) -> Range {
150+ // When the character under the cursor changes due to complete pair
151+ // insertion, we must look backward a grapheme and then add the length
152+ // of the insertion to put the resulting cursor in the right place, e.g.
153+ //
154+ // foo[\r\n] - anchor: 3, head: 5
155+ // foo([)]\r\n - anchor: 4, head: 5
156+ //
157+ // foo[\r\n] - anchor: 3, head: 5
158+ // foo'[\r\n] - anchor: 4, head: 6
159+ //
160+ // foo([)]\r\n - anchor: 4, head: 5
161+ // foo()[\r\n] - anchor: 5, head: 7
162+ //
163+ // [foo]\r\n - anchor: 0, head: 3
164+ // [foo(])\r\n - anchor: 0, head: 5
165+
166+ // inserting at the very end of the document after the last newline
167+ if start_range.head == doc.len_chars() && start_range.anchor == doc.len_chars() {
168+ return Range::new(
169+ start_range.anchor + offset + 1,
170+ start_range.head + offset + 1,
171+ );
172+ }
173+
174+ let doc_slice = doc.slice(..);
175+ let single_grapheme = start_range.is_single_grapheme(doc_slice);
176+
177+ // just skip over graphemes
178+ if len_inserted == 0 {
179+ let end_anchor = if single_grapheme {
180+ graphemes::next_grapheme_boundary(doc_slice, start_range.anchor) + offset
181+
182+ // even for backward inserts with multiple grapheme selections,
183+ // we want the anchor to stay where it is so that the relative
184+ // selection does not change, e.g.:
185+ //
186+ // foo([) wor]d -> insert ) -> foo()[ wor]d
187+ } else {
188+ start_range.anchor + offset
189+ };
190+
191+ return Range::new(
192+ end_anchor,
193+ graphemes::next_grapheme_boundary(doc_slice, start_range.head) + offset,
194+ );
195+ }
196+
197+ // trivial case: only inserted a single-char opener, just move the selection
198+ if len_inserted == 1 {
199+ let end_anchor = if single_grapheme || start_range.direction() == Direction::Backward {
200+ start_range.anchor + offset + 1
201+ } else {
202+ start_range.anchor + offset
203+ };
204+
205+ return Range::new(end_anchor, start_range.head + offset + 1);
206+ }
207+
208+ // If the head = 0, then we must be in insert mode with a backward
209+ // cursor, which implies the head will just move
210+ let end_head = if start_range.head == 0 || start_range.direction() == Direction::Backward {
211+ start_range.head + offset + 1
212+ } else {
213+ // We must have a forward cursor, which means we must move to the
214+ // other end of the grapheme to get to where the new characters
215+ // are inserted, then move the head to where it should be
216+ let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head);
217+ log::trace!(
218+ "prev_bound: {}, offset: {}, len_inserted: {}",
219+ prev_bound,
220+ offset,
221+ len_inserted
222+ );
223+ prev_bound + offset + len_inserted
224+ };
225+
226+ let end_anchor = match (start_range.len(), start_range.direction()) {
227+ // if we have a zero width cursor, it shifts to the same number
228+ (0, _) => end_head,
229+
230+ // If we are inserting for a regular one-width cursor, the anchor
231+ // moves with the head. This is the fast path for ASCII.
232+ (1, Direction::Forward) => end_head - 1,
233+ (1, Direction::Backward) => end_head + 1,
234+
235+ (_, Direction::Forward) => {
236+ if single_grapheme {
237+ graphemes::prev_grapheme_boundary(doc.slice(..), start_range.head) + 1
238+
239+ // if we are appending, the anchor stays where it is; only offset
240+ // for multiple range insertions
241+ } else {
242+ start_range.anchor + offset
243+ }
244+ }
245+
246+ (_, Direction::Backward) => {
247+ if single_grapheme {
248+ // if we're backward, then the head is at the first char
249+ // of the typed char, so we need to add the length of
250+ // the closing char
251+ graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor)
252+ + len_inserted
253+ + offset
254+ } else {
255+ // when we are inserting in front of a selection, we need to move
256+ // the anchor over by however many characters were inserted overall
257+ start_range.anchor + offset + len_inserted
258+ }
259+ }
260+ };
261+
262+ Range::new(end_anchor, end_head)
263+}
264+
265+fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
266+ let mut end_ranges = SmallVec::with_capacity(selection.len());
267+ let mut offs = 0;
268+
269+ let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
270+ let cursor = start_range.cursor(doc.slice(..));
271+ let next_char = doc.get_char(cursor);
272+ let len_inserted;
273+
274+ // Since auto pairs are currently limited to single chars, we're either
275+ // inserting exactly one or two chars. When arbitrary length pairs are
276+ // added, these will need to be changed.
277+ let change = match next_char {
278+ Some(_) if !pair.should_close(doc, start_range) => {
279+ len_inserted = 1;
280+ let mut tendril = Tendril::new();
281+ tendril.push(pair.open);
282+ (cursor, cursor, Some(tendril))
283+ }
284+ _ => {
285+ // insert open & close
286+ let pair_str = Tendril::from_iter([pair.open, pair.close]);
287+ len_inserted = 2;
288+ (cursor, cursor, Some(pair_str))
289+ }
290+ };
291+
292+ let next_range = get_next_range(doc, start_range, offs, len_inserted);
293+ end_ranges.push(next_range);
294+ offs += len_inserted;
295+
296+ change
297+ });
298+
299+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
300+ log::debug!("auto pair transaction: {:#?}", t);
301+ t
302+}
303+
304+fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
305+ let mut end_ranges = SmallVec::with_capacity(selection.len());
306+ let mut offs = 0;
307+
308+ let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
309+ let cursor = start_range.cursor(doc.slice(..));
310+ let next_char = doc.get_char(cursor);
311+ let mut len_inserted = 0;
312+
313+ let change = if next_char == Some(pair.close) {
314+ // return transaction that moves past close
315+ (cursor, cursor, None) // no-op
316+ } else {
317+ len_inserted = 1;
318+ let mut tendril = Tendril::new();
319+ tendril.push(pair.close);
320+ (cursor, cursor, Some(tendril))
321+ };
322+
323+ let next_range = get_next_range(doc, start_range, offs, len_inserted);
324+ end_ranges.push(next_range);
325+ offs += len_inserted;
326+
327+ change
328+ });
329+
330+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
331+ log::debug!("auto pair transaction: {:#?}", t);
332+ t
333+}
334+
335+/// handle cases where open and close is the same, or in triples ("""docstring""")
336+fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
337+ let mut end_ranges = SmallVec::with_capacity(selection.len());
338+
339+ let mut offs = 0;
340+
341+ let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
342+ let cursor = start_range.cursor(doc.slice(..));
343+ let mut len_inserted = 0;
344+ let next_char = doc.get_char(cursor);
345+
346+ let change = if next_char == Some(pair.open) {
347+ // return transaction that moves past close
348+ (cursor, cursor, None) // no-op
349+ } else {
350+ let mut pair_str = Tendril::new();
351+ pair_str.push(pair.open);
352+
353+ // for equal pairs, don't insert both open and close if either
354+ // side has a non-pair char
355+ if pair.should_close(doc, start_range) {
356+ pair_str.push(pair.close);
357+ }
358+
359+ len_inserted += pair_str.chars().count();
360+ (cursor, cursor, Some(pair_str))
361+ };
362+
363+ let next_range = get_next_range(doc, start_range, offs, len_inserted);
364+ end_ranges.push(next_range);
365+ offs += len_inserted;
366+
367+ change
368+ });
369+
370+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
371+ log::debug!("auto pair transaction: {:#?}", t);
372+ t
373+}
helix-core/src/chars.rsnew file+137
@@ -0,0 +1,137 @@
1+//! Utility functions to categorize a `char`.
2+
3+use crate::LineEnding;
4+
5+#[derive(Debug, Eq, PartialEq)]
6+pub enum CharCategory {
7+ Whitespace,
8+ Eol,
9+ Word,
10+ Punctuation,
11+ Unknown,
12+}
13+
14+#[inline]
15+pub fn categorize_char(ch: char) -> CharCategory {
16+ if char_is_line_ending(ch) {
17+ CharCategory::Eol
18+ } else if ch.is_whitespace() {
19+ CharCategory::Whitespace
20+ } else if char_is_word(ch) {
21+ CharCategory::Word
22+ } else if char_is_punctuation(ch) {
23+ CharCategory::Punctuation
24+ } else {
25+ CharCategory::Unknown
26+ }
27+}
28+
29+/// Determine whether a character is a line ending.
30+#[inline]
31+pub fn char_is_line_ending(ch: char) -> bool {
32+ LineEnding::from_char(ch).is_some()
33+}
34+
35+/// Determine whether a character qualifies as (non-line-break)
36+/// whitespace.
37+#[inline]
38+pub fn char_is_whitespace(ch: char) -> bool {
39+ // TODO: this is a naive binary categorization of whitespace
40+ // characters. For display, word wrapping, etc. we'll need a better
41+ // categorization based on e.g. breaking vs non-breaking spaces
42+ // and whether they're zero-width or not.
43+ match ch {
44+ //'\u{1680}' | // Ogham Space Mark (here for completeness, but usually displayed as a dash, not as whitespace)
45+ '\u{0009}' | // Character Tabulation
46+ '\u{0020}' | // Space
47+ '\u{00A0}' | // No-break Space
48+ '\u{180E}' | // Mongolian Vowel Separator
49+ '\u{202F}' | // Narrow No-break Space
50+ '\u{205F}' | // Medium Mathematical Space
51+ '\u{3000}' | // Ideographic Space
52+ '\u{FEFF}' // Zero Width No-break Space
53+ => true,
54+
55+ // En Quad, Em Quad, En Space, Em Space, Three-per-em Space,
56+ // Four-per-em Space, Six-per-em Space, Figure Space,
57+ // Punctuation Space, Thin Space, Hair Space, Zero Width Space.
58+ ch if ('\u{2000}' ..= '\u{200B}').contains(&ch) => true,
59+
60+ _ => false,
61+ }
62+}
63+
64+#[inline]
65+pub fn char_is_punctuation(ch: char) -> bool {
66+ use unicode_general_category::{get_general_category, GeneralCategory};
67+
68+ matches!(
69+ get_general_category(ch),
70+ GeneralCategory::OtherPunctuation
71+ | GeneralCategory::OpenPunctuation
72+ | GeneralCategory::ClosePunctuation
73+ | GeneralCategory::InitialPunctuation
74+ | GeneralCategory::FinalPunctuation
75+ | GeneralCategory::ConnectorPunctuation
76+ | GeneralCategory::DashPunctuation
77+ | GeneralCategory::MathSymbol
78+ | GeneralCategory::CurrencySymbol
79+ | GeneralCategory::ModifierSymbol
80+ )
81+}
82+
83+#[inline]
84+pub fn char_is_word(ch: char) -> bool {
85+ ch.is_alphanumeric() || ch == '_'
86+}
87+
88+#[cfg(test)]
89+mod test {
90+ use super::*;
91+
92+ #[test]
93+ fn test_categorize() {
94+ #[cfg(not(feature = "unicode-lines"))]
95+ const EOL_TEST_CASE: &str = "\n";
96+ #[cfg(feature = "unicode-lines")]
97+ const EOL_TEST_CASE: &str = "\n\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}";
98+ const WORD_TEST_CASE: &str = "_hello_world_あいうえおー12345678901234567890";
99+ const PUNCTUATION_TEST_CASE: &str =
100+ "!\"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~!”#$%&’()*+、。:;<=>?@「」^`{|}~";
101+ const WHITESPACE_TEST_CASE: &str = "      ";
102+
103+ for ch in EOL_TEST_CASE.chars() {
104+ assert_eq!(CharCategory::Eol, categorize_char(ch));
105+ }
106+
107+ for ch in WHITESPACE_TEST_CASE.chars() {
108+ assert_eq!(
109+ CharCategory::Whitespace,
110+ categorize_char(ch),
111+ "Testing '{}', but got `{:?}` instead of `Category::Whitespace`",
112+ ch,
113+ categorize_char(ch)
114+ );
115+ }
116+
117+ for ch in WORD_TEST_CASE.chars() {
118+ assert_eq!(
119+ CharCategory::Word,
120+ categorize_char(ch),
121+ "Testing '{}', but got `{:?}` instead of `Category::Word`",
122+ ch,
123+ categorize_char(ch)
124+ );
125+ }
126+
127+ for ch in PUNCTUATION_TEST_CASE.chars() {
128+ assert_eq!(
129+ CharCategory::Punctuation,
130+ categorize_char(ch),
131+ "Testing '{}', but got `{:?}` instead of `Category::Punctuation`",
132+ ch,
133+ categorize_char(ch)
134+ );
135+ }
136+ }
137+}
helix-core/src/comment.rsnew file+152
@@ -0,0 +1,152 @@
1+//! This module contains the functionality toggle comments on lines over the selection
2+//! using the comment character defined in the user's `languages.toml`
3+
4+use crate::{
5+ find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction,
6+};
7+use std::borrow::Cow;
8+
9+/// Given text, a comment token, and a set of line indices, returns the following:
10+/// - Whether the given lines should be considered commented
11+/// - If any of the lines are uncommented, all lines are considered as such.
12+/// - The lines to change for toggling comments
13+/// - This is all provided lines excluding blanks lines.
14+/// - The column of the comment tokens
15+/// - Column of existing tokens, if the lines are commented; column to place tokens at otherwise.
16+/// - The margin to the right of the comment tokens
17+/// - Defaults to `1`. If any existing comment token is not followed by a space, changes to `0`.
18+fn find_line_comment(
19+ token: &str,
20+ text: RopeSlice,
21+ lines: impl IntoIterator<Item = usize>,
22+) -> (bool, Vec<usize>, usize, usize) {
23+ let mut commented = true;
24+ let mut to_change = Vec::new();
25+ let mut min = usize::MAX; // minimum col for find_first_non_whitespace_char
26+ let mut margin = 1;
27+ let token_len = token.chars().count();
28+ for line in lines {
29+ let line_slice = text.line(line);
30+ if let Some(pos) = find_first_non_whitespace_char(line_slice) {
31+ let len = line_slice.len_chars();
32+
33+ if pos < min {
34+ min = pos;
35+ }
36+
37+ // line can be shorter than pos + token len
38+ let fragment = Cow::from(line_slice.slice(pos..std::cmp::min(pos + token.len(), len)));
39+
40+ if fragment != token {
41+ // as soon as one of the non-blank lines doesn't have a comment, the whole block is
42+ // considered uncommented.
43+ commented = false;
44+ }
45+
46+ // determine margin of 0 or 1 for uncommenting; if any comment token is not followed by a space,
47+ // a margin of 0 is used for all lines.
48+ if !matches!(line_slice.get_char(pos + token_len), Some(c) if c == ' ') {
49+ margin = 0;
50+ }
51+
52+ // blank lines don't get pushed.
53+ to_change.push(line);
54+ }
55+ }
56+ (commented, to_change, min, margin)
57+}
58+
59+#[must_use]
60+pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&str>) -> Transaction {
61+ let text = doc.slice(..);
62+
63+ let token = token.unwrap_or("//");
64+ let comment = Tendril::from(format!("{} ", token));
65+
66+ let mut lines: Vec<usize> = Vec::with_capacity(selection.len());
67+
68+ let mut min_next_line = 0;
69+ for selection in selection {
70+ let (start, end) = selection.line_range(text);
71+ let start = start.clamp(min_next_line, text.len_lines());
72+ let end = (end + 1).min(text.len_lines());
73+
74+ lines.extend(start..end);
75+ min_next_line = end;
76+ }
77+
78+ let (commented, to_change, min, margin) = find_line_comment(token, text, lines);
79+
80+ let mut changes: Vec<Change> = Vec::with_capacity(to_change.len());
81+
82+ for line in to_change {
83+ let pos = text.line_to_char(line) + min;
84+
85+ if !commented {
86+ // comment line
87+ changes.push((pos, pos, Some(comment.clone())));
88+ } else {
89+ // uncomment line
90+ changes.push((pos, pos + token.len() + margin, None));
91+ }
92+ }
93+
94+ Transaction::change(doc, changes.into_iter())
95+}
96+
97+#[cfg(test)]
98+mod test {
99+ use super::*;
100+
101+ #[test]
102+ fn test_find_line_comment() {
103+ // four lines, two space indented, except for line 1 which is blank.
104+ let mut doc = Rope::from(" 1\n\n 2\n 3");
105+ // select whole document
106+ let mut selection = Selection::single(0, doc.len_chars() - 1);
107+
108+ let text = doc.slice(..);
109+
110+ let res = find_line_comment("//", text, 0..3);
111+ // (commented = true, to_change = [line 0, line 2], min = col 2, margin = 0)
112+ assert_eq!(res, (false, vec![0, 2], 2, 0));
113+
114+ // comment
115+ let transaction = toggle_line_comments(&doc, &selection, None);
116+ transaction.apply(&mut doc);
117+ selection = selection.map(transaction.changes());
118+
119+ assert_eq!(doc, " // 1\n\n // 2\n // 3");
120+
121+ // uncomment
122+ let transaction = toggle_line_comments(&doc, &selection, None);
123+ transaction.apply(&mut doc);
124+ selection = selection.map(transaction.changes());
125+ assert_eq!(doc, " 1\n\n 2\n 3");
126+ assert!(selection.len() == 1); // to ignore the selection unused warning
127+
128+ // 0 margin comments
129+ doc = Rope::from(" //1\n\n //2\n //3");
130+ // reset the selection.
131+ selection = Selection::single(0, doc.len_chars() - 1);
132+
133+ let transaction = toggle_line_comments(&doc, &selection, None);
134+ transaction.apply(&mut doc);
135+ selection = selection.map(transaction.changes());
136+ assert_eq!(doc, " 1\n\n 2\n 3");
137+ assert!(selection.len() == 1); // to ignore the selection unused warning
138+
139+ // 0 margin comments, with no space
140+ doc = Rope::from("//");
141+ // reset the selection.
142+ selection = Selection::single(0, doc.len_chars() - 1);
143+
144+ let transaction = toggle_line_comments(&doc, &selection, None);
145+ transaction.apply(&mut doc);
146+ selection = selection.map(transaction.changes());
147+ assert_eq!(doc, "");
148+ assert!(selection.len() == 1); // to ignore the selection unused warning
149+
150+ // TODO: account for uncommenting with uneven comment indentation
151+ }
152+}
helix-core/src/config.rsnew file+10
@@ -0,0 +1,10 @@
1+/// Syntax configuration loader based on built-in languages.toml.
2+pub fn default_syntax_loader() -> crate::syntax::Configuration {
3+ helix_loader::config::default_lang_config()
4+ .try_into()
5+ .expect("Could not serialize built-in languages.toml")
6+}
7+/// Syntax configuration loader based on user configured languages.toml.
8+pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
9+ helix_loader::config::user_lang_config()?.try_into()
10+}
helix-core/src/diagnostic.rs+49−2
@@ -1,7 +1,54 @@
1−use crate::Range;
1+//! LSP diagnostic utility types.
2+use serde::{Deserialize, Serialize};
2
3
4+/// Describes the severity level of a [`Diagnostic`].
5+#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
6+pub enum Severity {
7+ Hint,
8+ Info,
9+ Warning,
10+ Error,
11+}
12+
13+impl Default for Severity {
14+ fn default() -> Self {
15+ Self::Hint
16+ }
17+}
18+
19+/// A range of `char`s within the text.
20+#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
21+pub struct Range {
22+ pub start: usize,
23+ pub end: usize,
24+}
25+
26+#[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)]
27+pub enum NumberOrString {
28+ Number(i32),
29+ String(String),
30+}
31+
32+#[derive(Debug, Clone)]
33+pub enum DiagnosticTag {
34+ Unnecessary,
35+ Deprecated,
36+}
37+
38+/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.94.0/lsp_types/struct.Diagnostic.html)
39+#[derive(Debug, Clone)]
3pub struct Diagnostic {
40pub struct Diagnostic {
4− pub range: (usize, usize),
41+ pub range: Range,
42+ // whether this diagnostic ends at the end of(or inside) a word
43+ pub ends_at_word: bool,
44+ pub starts_at_word: bool,
45+ pub zero_width: bool,
5 pub line: usize,
46 pub line: usize,
6 pub message: String,
47 pub message: String,
48+ pub severity: Option<Severity>,
49+ pub code: Option<NumberOrString>,
50+ pub language_server_id: usize,
51+ pub tags: Vec<DiagnosticTag>,
52+ pub source: Option<String>,
53+ pub data: Option<serde_json::Value>,
7}
54}
helix-core/src/diff.rsnew file+221
@@ -0,0 +1,221 @@
1+use std::ops::Range;
2+use std::time::Instant;
3+
4+use imara_diff::intern::InternedInput;
5+use imara_diff::Algorithm;
6+use ropey::RopeSlice;
7+
8+use crate::{ChangeSet, Rope, Tendril, Transaction};
9+
10+/// A `imara_diff::Sink` that builds a `ChangeSet` for a character diff of a hunk
11+struct CharChangeSetBuilder<'a> {
12+ res: &'a mut ChangeSet,
13+ hunk: &'a InternedInput<char>,
14+ pos: u32,
15+}
16+
17+impl imara_diff::Sink for CharChangeSetBuilder<'_> {
18+ type Out = ();
19+ fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
20+ self.res.retain((before.start - self.pos) as usize);
21+ self.res.delete(before.len());
22+ self.pos = before.end;
23+
24+ let res = self.hunk.after[after.start as usize..after.end as usize]
25+ .iter()
26+ .map(|&token| self.hunk.interner[token])
27+ .collect();
28+
29+ self.res.insert(res);
30+ }
31+
32+ fn finish(self) -> Self::Out {
33+ self.res.retain(self.hunk.before.len() - self.pos as usize);
34+ }
35+}
36+
37+struct LineChangeSetBuilder<'a> {
38+ res: ChangeSet,
39+ after: RopeSlice<'a>,
40+ file: &'a InternedInput<RopeSlice<'a>>,
41+ current_hunk: InternedInput<char>,
42+ pos: u32,
43+}
44+
45+impl imara_diff::Sink for LineChangeSetBuilder<'_> {
46+ type Out = ChangeSet;
47+
48+ fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
49+ let len = self.file.before[self.pos as usize..before.start as usize]
50+ .iter()
51+ .map(|&it| self.file.interner[it].len_chars())
52+ .sum();
53+ self.res.retain(len);
54+ self.pos = before.end;
55+
56+ // do not perform diffs on large hunks
57+ let len_before = before.end - before.start;
58+ let len_after = after.end - after.start;
59+
60+ // Pure insertions/removals do not require a character diff.
61+ // Very large changes are ignored because their character diff is expensive to compute
62+ // TODO adjust heuristic to detect large changes?
63+ if len_before == 0
64+ || len_after == 0
65+ || len_after > 5 * len_before
66+ || 5 * len_after < len_before && len_before > 10
67+ || len_before + len_after > 200
68+ {
69+ let remove = self.file.before[before.start as usize..before.end as usize]
70+ .iter()
71+ .map(|&it| self.file.interner[it].len_chars())
72+ .sum();
73+ self.res.delete(remove);
74+ let mut fragment = Tendril::new();
75+ if len_after > 500 {
76+ // copying a rope line by line is slower then copying the entire
77+ // rope. Use to_string for very large changes instead..
78+ if self.file.after.len() == after.end as usize {
79+ if after.start == 0 {
80+ fragment = self.after.to_string().into();
81+ } else {
82+ let start = self.after.line_to_char(after.start as usize);
83+ fragment = self.after.slice(start..).to_string().into();
84+ }
85+ } else if after.start == 0 {
86+ let end = self.after.line_to_char(after.end as usize);
87+ fragment = self.after.slice(..end).to_string().into();
88+ } else {
89+ let start = self.after.line_to_char(after.start as usize);
90+ let end = self.after.line_to_char(after.end as usize);
91+ fragment = self.after.slice(start..end).to_string().into();
92+ }
93+ } else {
94+ for &line in &self.file.after[after.start as usize..after.end as usize] {
95+ for chunk in self.file.interner[line].chunks() {
96+ fragment.push_str(chunk)
97+ }
98+ }
99+ };
100+ self.res.insert(fragment);
101+ } else {
102+ // for reasonably small hunks, generating a ChangeSet from char diff can save memory
103+ // TODO use a tokenizer (word diff?) for improved performance
104+ let hunk_before = self.file.before[before.start as usize..before.end as usize]
105+ .iter()
106+ .flat_map(|&it| self.file.interner[it].chars());
107+ let hunk_after = self.file.after[after.start as usize..after.end as usize]
108+ .iter()
109+ .flat_map(|&it| self.file.interner[it].chars());
110+ self.current_hunk.update_before(hunk_before);
111+ self.current_hunk.update_after(hunk_after);
112+
113+ // the histogram heuristic does not work as well
114+ // for characters because the same characters often reoccur
115+ // use myer diff instead
116+ imara_diff::diff(
117+ Algorithm::Myers,
118+ &self.current_hunk,
119+ CharChangeSetBuilder {
120+ res: &mut self.res,
121+ hunk: &self.current_hunk,
122+ pos: 0,
123+ },
124+ );
125+
126+ self.current_hunk.clear();
127+ }
128+ }
129+
130+ fn finish(mut self) -> Self::Out {
131+ let len = self.file.before[self.pos as usize..]
132+ .iter()
133+ .map(|&it| self.file.interner[it].len_chars())
134+ .sum();
135+
136+ self.res.retain(len);
137+ self.res
138+ }
139+}
140+
141+struct RopeLines<'a>(RopeSlice<'a>);
142+
143+impl<'a> imara_diff::intern::TokenSource for RopeLines<'a> {
144+ type Token = RopeSlice<'a>;
145+ type Tokenizer = ropey::iter::Lines<'a>;
146+
147+ fn tokenize(&self) -> Self::Tokenizer {
148+ self.0.lines()
149+ }
150+
151+ fn estimate_tokens(&self) -> u32 {
152+ // we can provide a perfect estimate which is very nice for performance
153+ self.0.len_lines() as u32
154+ }
155+}
156+
157+/// Compares `old` and `new` to generate a [`Transaction`] describing
158+/// the steps required to get from `old` to `new`.
159+pub fn compare_ropes(before: &Rope, after: &Rope) -> Transaction {
160+ let start = Instant::now();
161+ let res = ChangeSet::with_capacity(32);
162+ let after = after.slice(..);
163+ let file = InternedInput::new(RopeLines(before.slice(..)), RopeLines(after));
164+ let builder = LineChangeSetBuilder {
165+ res,
166+ file: &file,
167+ after,
168+ pos: 0,
169+ current_hunk: InternedInput::default(),
170+ };
171+
172+ let res = imara_diff::diff(Algorithm::Histogram, &file, builder).into();
173+
174+ log::debug!(
175+ "rope diff took {}s",
176+ Instant::now().duration_since(start).as_secs_f64()
177+ );
178+ res
179+}
180+
181+#[cfg(test)]
182+mod tests {
183+ use super::*;
184+
185+ fn test_identity(a: &str, b: &str) {
186+ let mut old = Rope::from(a);
187+ let new = Rope::from(b);
188+ compare_ropes(&old, &new).apply(&mut old);
189+ assert_eq!(old, new);
190+ }
191+
192+ quickcheck::quickcheck! {
193+ fn test_compare_ropes(a: String, b: String) -> bool {
194+ let mut old = Rope::from(a);
195+ let new = Rope::from(b);
196+ compare_ropes(&old, &new).apply(&mut old);
197+ old == new
198+ }
199+ }
200+
201+ #[test]
202+ fn equal_files() {
203+ test_identity("foo", "foo");
204+ }
205+
206+ #[test]
207+ fn trailing_newline() {
208+ test_identity("foo\n", "foo");
209+ test_identity("foo", "foo\n");
210+ }
211+
212+ #[test]
213+ fn new_file() {
214+ test_identity("", "foo");
215+ }
216+
217+ #[test]
218+ fn deleted_file() {
219+ test_identity("foo", "");
220+ }
221+}
helix-core/src/doc_formatter.rsnew file+384
@@ -0,0 +1,384 @@
1+//! The `DocumentFormatter` forms the bridge between the raw document text
2+//! and onscreen positioning. It yields the text graphemes as an iterator
3+//! and traverses (part) of the document text. During that traversal it
4+//! handles grapheme detection, softwrapping and annotations.
5+//! It yields `FormattedGrapheme`s and their corresponding visual coordinates.
6+//!
7+//! As both virtual text and softwrapping can insert additional lines into the document
8+//! it is generally not possible to find the start of the previous visual line.
9+//! Instead the `DocumentFormatter` starts at the last "checkpoint" (usually a linebreak)
10+//! called a "block" and the caller must advance it as needed.
11+
12+use std::borrow::Cow;
13+use std::fmt::Debug;
14+use std::mem::{replace, take};
15+
16+#[cfg(test)]
17+mod test;
18+
19+use unicode_segmentation::{Graphemes, UnicodeSegmentation};
20+
21+use crate::graphemes::{Grapheme, GraphemeStr};
22+use crate::syntax::Highlight;
23+use crate::text_annotations::TextAnnotations;
24+use crate::{Position, RopeGraphemes, RopeSlice};
25+
26+/// TODO make Highlight a u32 to reduce the size of this enum to a single word.
27+#[derive(Debug, Clone, Copy)]
28+pub enum GraphemeSource {
29+ Document {
30+ codepoints: u32,
31+ },
32+ /// Inline virtual text can not be highlighted with a `Highlight` iterator
33+ /// because it's not part of the document. Instead the `Highlight`
34+ /// is emitted right by the document formatter
35+ VirtualText {
36+ highlight: Option<Highlight>,
37+ },
38+}
39+
40+#[derive(Debug, Clone)]
41+pub struct FormattedGrapheme<'a> {
42+ pub grapheme: Grapheme<'a>,
43+ pub source: GraphemeSource,
44+}
45+
46+impl<'a> FormattedGrapheme<'a> {
47+ pub fn new(
48+ g: GraphemeStr<'a>,
49+ visual_x: usize,
50+ tab_width: u16,
51+ source: GraphemeSource,
52+ ) -> FormattedGrapheme<'a> {
53+ FormattedGrapheme {
54+ grapheme: Grapheme::new(g, visual_x, tab_width),
55+ source,
56+ }
57+ }
58+ /// Returns whether this grapheme is virtual inline text
59+ pub fn is_virtual(&self) -> bool {
60+ matches!(self.source, GraphemeSource::VirtualText { .. })
61+ }
62+
63+ pub fn placeholder() -> Self {
64+ FormattedGrapheme {
65+ grapheme: Grapheme::Other { g: " ".into() },
66+ source: GraphemeSource::Document { codepoints: 0 },
67+ }
68+ }
69+
70+ pub fn doc_chars(&self) -> usize {
71+ match self.source {
72+ GraphemeSource::Document { codepoints } => codepoints as usize,
73+ GraphemeSource::VirtualText { .. } => 0,
74+ }
75+ }
76+
77+ pub fn is_whitespace(&self) -> bool {
78+ self.grapheme.is_whitespace()
79+ }
80+
81+ pub fn width(&self) -> usize {
82+ self.grapheme.width()
83+ }
84+
85+ pub fn is_word_boundary(&self) -> bool {
86+ self.grapheme.is_word_boundary()
87+ }
88+}
89+
90+#[derive(Debug, Clone)]
91+pub struct TextFormat {
92+ pub soft_wrap: bool,
93+ pub tab_width: u16,
94+ pub max_wrap: u16,
95+ pub max_indent_retain: u16,
96+ pub wrap_indicator: Box<str>,
97+ pub wrap_indicator_highlight: Option<Highlight>,
98+ pub viewport_width: u16,
99+}
100+
101+// test implementation is basically only used for testing or when softwrap is always disabled
102+impl Default for TextFormat {
103+ fn default() -> Self {
104+ TextFormat {
105+ soft_wrap: false,
106+ tab_width: 4,
107+ max_wrap: 3,
108+ max_indent_retain: 4,
109+ wrap_indicator: Box::from(" "),
110+ viewport_width: 17,
111+ wrap_indicator_highlight: None,
112+ }
113+ }
114+}
115+
116+#[derive(Debug)]
117+pub struct DocumentFormatter<'t> {
118+ text_fmt: &'t TextFormat,
119+ annotations: &'t TextAnnotations,
120+
121+ /// The visual position at the end of the last yielded word boundary
122+ visual_pos: Position,
123+ graphemes: RopeGraphemes<'t>,
124+ /// The character pos of the `graphemes` iter used for inserting annotations
125+ char_pos: usize,
126+ /// The line pos of the `graphemes` iter used for inserting annotations
127+ line_pos: usize,
128+ exhausted: bool,
129+
130+ /// Line breaks to be reserved for virtual text
131+ /// at the next line break
132+ virtual_lines: usize,
133+ inline_anntoation_graphemes: Option<(Graphemes<'t>, Option<Highlight>)>,
134+
135+ // softwrap specific
136+ /// The indentation of the current line
137+ /// Is set to `None` if the indentation level is not yet known
138+ /// because no non-whitespace graphemes have been encountered yet
139+ indent_level: Option<usize>,
140+ /// In case a long word needs to be split a single grapheme might need to be wrapped
141+ /// while the rest of the word stays on the same line
142+ peeked_grapheme: Option<(FormattedGrapheme<'t>, usize)>,
143+ /// A first-in first-out (fifo) buffer for the Graphemes of any given word
144+ word_buf: Vec<FormattedGrapheme<'t>>,
145+ /// The index of the next grapheme that will be yielded from the `word_buf`
146+ word_i: usize,
147+}
148+
149+impl<'t> DocumentFormatter<'t> {
150+ /// Creates a new formatter at the last block before `char_idx`.
151+ /// A block is a chunk which always ends with a linebreak.
152+ /// This is usually just a normal line break.
153+ /// However very long lines are always wrapped at constant intervals that can be cheaply calculated
154+ /// to avoid pathological behaviour.
155+ pub fn new_at_prev_checkpoint(
156+ text: RopeSlice<'t>,
157+ text_fmt: &'t TextFormat,
158+ annotations: &'t TextAnnotations,
159+ char_idx: usize,
160+ ) -> (Self, usize) {
161+ // TODO divide long lines into blocks to avoid bad performance for long lines
162+ let block_line_idx = text.char_to_line(char_idx.min(text.len_chars()));
163+ let block_char_idx = text.line_to_char(block_line_idx);
164+ annotations.reset_pos(block_char_idx);
165+ (
166+ DocumentFormatter {
167+ text_fmt,
168+ annotations,
169+ visual_pos: Position { row: 0, col: 0 },
170+ graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
171+ char_pos: block_char_idx,
172+ exhausted: false,
173+ virtual_lines: 0,
174+ indent_level: None,
175+ peeked_grapheme: None,
176+ word_buf: Vec::with_capacity(64),
177+ word_i: 0,
178+ line_pos: block_line_idx,
179+ inline_anntoation_graphemes: None,
180+ },
181+ block_char_idx,
182+ )
183+ }
184+
185+ fn next_inline_annotation_grapheme(&mut self) -> Option<(&'t str, Option<Highlight>)> {
186+ loop {
187+ if let Some(&mut (ref mut annotation, highlight)) =
188+ self.inline_anntoation_graphemes.as_mut()
189+ {
190+ if let Some(grapheme) = annotation.next() {
191+ return Some((grapheme, highlight));
192+ }
193+ }
194+
195+ if let Some((annotation, highlight)) =
196+ self.annotations.next_inline_annotation_at(self.char_pos)
197+ {
198+ self.inline_anntoation_graphemes = Some((
199+ UnicodeSegmentation::graphemes(&*annotation.text, true),
200+ highlight,
201+ ))
202+ } else {
203+ return None;
204+ }
205+ }
206+ }
207+
208+ fn advance_grapheme(&mut self, col: usize) -> Option<FormattedGrapheme<'t>> {
209+ let (grapheme, source) =
210+ if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme() {
211+ (grapheme.into(), GraphemeSource::VirtualText { highlight })
212+ } else if let Some(grapheme) = self.graphemes.next() {
213+ self.virtual_lines += self.annotations.annotation_lines_at(self.char_pos);
214+ let codepoints = grapheme.len_chars() as u32;
215+
216+ let overlay = self.annotations.overlay_at(self.char_pos);
217+ let grapheme = match overlay {
218+ Some((overlay, _)) => overlay.grapheme.as_str().into(),
219+ None => Cow::from(grapheme).into(),
220+ };
221+
222+ self.char_pos += codepoints as usize;
223+ (grapheme, GraphemeSource::Document { codepoints })
224+ } else {
225+ if self.exhausted {
226+ return None;
227+ }
228+ self.exhausted = true;
229+ // EOF grapheme is required for rendering
230+ // and correct position computations
231+ return Some(FormattedGrapheme {
232+ grapheme: Grapheme::Other { g: " ".into() },
233+ source: GraphemeSource::Document { codepoints: 0 },
234+ });
235+ };
236+
237+ let grapheme = FormattedGrapheme::new(grapheme, col, self.text_fmt.tab_width, source);
238+
239+ Some(grapheme)
240+ }
241+
242+ /// Move a word to the next visual line
243+ fn wrap_word(&mut self, virtual_lines_before_word: usize) -> usize {
244+ // softwrap this word to the next line
245+ let indent_carry_over = if let Some(indent) = self.indent_level {
246+ if indent as u16 <= self.text_fmt.max_indent_retain {
247+ indent as u16
248+ } else {
249+ 0
250+ }
251+ } else {
252+ // ensure the indent stays 0
253+ self.indent_level = Some(0);
254+ 0
255+ };
256+
257+ self.visual_pos.col = indent_carry_over as usize;
258+ self.virtual_lines -= virtual_lines_before_word;
259+ self.visual_pos.row += 1 + virtual_lines_before_word;
260+ let mut i = 0;
261+ let mut word_width = 0;
262+ let wrap_indicator = UnicodeSegmentation::graphemes(&*self.text_fmt.wrap_indicator, true)
263+ .map(|g| {
264+ i += 1;
265+ let grapheme = FormattedGrapheme::new(
266+ g.into(),
267+ self.visual_pos.col + word_width,
268+ self.text_fmt.tab_width,
269+ GraphemeSource::VirtualText {
270+ highlight: self.text_fmt.wrap_indicator_highlight,
271+ },
272+ );
273+ word_width += grapheme.width();
274+ grapheme
275+ });
276+ self.word_buf.splice(0..0, wrap_indicator);
277+
278+ for grapheme in &mut self.word_buf[i..] {
279+ let visual_x = self.visual_pos.col + word_width;
280+ grapheme
281+ .grapheme
282+ .change_position(visual_x, self.text_fmt.tab_width);
283+ word_width += grapheme.width();
284+ }
285+ word_width
286+ }
287+
288+ fn advance_to_next_word(&mut self) {
289+ self.word_buf.clear();
290+ let mut word_width = 0;
291+ let virtual_lines_before_word = self.virtual_lines;
292+ let mut virtual_lines_before_grapheme = self.virtual_lines;
293+
294+ loop {
295+ // softwrap word if necessary
296+ if word_width + self.visual_pos.col >= self.text_fmt.viewport_width as usize {
297+ // wrapping this word would move too much text to the next line
298+ // split the word at the line end instead
299+ if word_width > self.text_fmt.max_wrap as usize {
300+ // Usually we stop accomulating graphemes as soon as softwrapping becomes necessary.
301+ // However if the last grapheme is multiple columns wide it might extend beyond the EOL.
302+ // The condition below ensures that this grapheme is not cutoff and instead wrapped to the next line
303+ if word_width + self.visual_pos.col > self.text_fmt.viewport_width as usize {
304+ self.peeked_grapheme = self.word_buf.pop().map(|grapheme| {
305+ (grapheme, self.virtual_lines - virtual_lines_before_grapheme)
306+ });
307+ self.virtual_lines = virtual_lines_before_grapheme;
308+ }
309+ return;
310+ }
311+
312+ word_width = self.wrap_word(virtual_lines_before_word);
313+ }
314+
315+ virtual_lines_before_grapheme = self.virtual_lines;
316+
317+ let grapheme = if let Some((grapheme, virtual_lines)) = self.peeked_grapheme.take() {
318+ self.virtual_lines += virtual_lines;
319+ grapheme
320+ } else if let Some(grapheme) = self.advance_grapheme(self.visual_pos.col + word_width) {
321+ grapheme
322+ } else {
323+ return;
324+ };
325+
326+ // Track indentation
327+ if !grapheme.is_whitespace() && self.indent_level.is_none() {
328+ self.indent_level = Some(self.visual_pos.col);
329+ } else if grapheme.grapheme == Grapheme::Newline {
330+ self.indent_level = None;
331+ }
332+
333+ let is_word_boundary = grapheme.is_word_boundary();
334+ word_width += grapheme.width();
335+ self.word_buf.push(grapheme);
336+
337+ if is_word_boundary {
338+ return;
339+ }
340+ }
341+ }
342+
343+ /// returns the document line pos of the **next** grapheme that will be yielded
344+ pub fn line_pos(&self) -> usize {
345+ self.line_pos
346+ }
347+
348+ /// returns the visual pos of the **next** grapheme that will be yielded
349+ pub fn visual_pos(&self) -> Position {
350+ self.visual_pos
351+ }
352+}
353+
354+impl<'t> Iterator for DocumentFormatter<'t> {
355+ type Item = (FormattedGrapheme<'t>, Position);
356+
357+ fn next(&mut self) -> Option<Self::Item> {
358+ let grapheme = if self.text_fmt.soft_wrap {
359+ if self.word_i >= self.word_buf.len() {
360+ self.advance_to_next_word();
361+ self.word_i = 0;
362+ }
363+ let grapheme = replace(
364+ self.word_buf.get_mut(self.word_i)?,
365+ FormattedGrapheme::placeholder(),
366+ );
367+ self.word_i += 1;
368+ grapheme
369+ } else {
370+ self.advance_grapheme(self.visual_pos.col)?
371+ };
372+
373+ let pos = self.visual_pos;
374+ if grapheme.grapheme == Grapheme::Newline {
375+ self.visual_pos.row += 1;
376+ self.visual_pos.row += take(&mut self.virtual_lines);
377+ self.visual_pos.col = 0;
378+ self.line_pos += 1;
379+ } else {
380+ self.visual_pos.col += grapheme.width();
381+ }
382+ Some((grapheme, pos))
383+ }
384+}
helix-core/src/doc_formatter/test.rsnew file+182
@@ -0,0 +1,182 @@
1+use std::rc::Rc;
2+
3+use crate::doc_formatter::{DocumentFormatter, TextFormat};
4+use crate::text_annotations::{InlineAnnotation, Overlay, TextAnnotations};
5+
6+impl TextFormat {
7+ fn new_test(softwrap: bool) -> Self {
8+ TextFormat {
9+ soft_wrap: softwrap,
10+ tab_width: 2,
11+ max_wrap: 3,
12+ max_indent_retain: 4,
13+ wrap_indicator: ".".into(),
14+ wrap_indicator_highlight: None,
15+ // use a prime number to allow lining up too often with repeat
16+ viewport_width: 17,
17+ }
18+ }
19+}
20+
21+impl<'t> DocumentFormatter<'t> {
22+ fn collect_to_str(&mut self) -> String {
23+ use std::fmt::Write;
24+ let mut res = String::new();
25+ let viewport_width = self.text_fmt.viewport_width;
26+ let mut line = 0;
27+
28+ for (grapheme, pos) in self {
29+ if pos.row != line {
30+ line += 1;
31+ assert_eq!(pos.row, line);
32+ write!(res, "\n{}", ".".repeat(pos.col)).unwrap();
33+ assert!(
34+ pos.col <= viewport_width as usize,
35+ "softwrapped failed {}<={viewport_width}",
36+ pos.col
37+ );
38+ }
39+ write!(res, "{}", grapheme.grapheme).unwrap();
40+ }
41+
42+ res
43+ }
44+}
45+
46+fn softwrap_text(text: &str) -> String {
47+ DocumentFormatter::new_at_prev_checkpoint(
48+ text.into(),
49+ &TextFormat::new_test(true),
50+ &TextAnnotations::default(),
51+ 0,
52+ )
53+ .0
54+ .collect_to_str()
55+}
56+
57+#[test]
58+fn basic_softwrap() {
59+ assert_eq!(
60+ softwrap_text(&"foo ".repeat(10)),
61+ "foo foo foo foo \n.foo foo foo foo \n.foo foo "
62+ );
63+ assert_eq!(
64+ softwrap_text(&"fooo ".repeat(10)),
65+ "fooo fooo fooo \n.fooo fooo fooo \n.fooo fooo fooo \n.fooo "
66+ );
67+
68+ // check that we don't wrap unnecessarily
69+ assert_eq!(softwrap_text("\t\txxxx1xxxx2xx\n"), " xxxx1xxxx2xx \n ");
70+}
71+
72+#[test]
73+fn softwrap_indentation() {
74+ assert_eq!(
75+ softwrap_text("\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
76+ " foo1 foo2 \n.....foo3 foo4 \n.....foo5 foo6 \n "
77+ );
78+ assert_eq!(
79+ softwrap_text("\t\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
80+ " foo1 foo2 \n.foo3 foo4 foo5 \n.foo6 \n "
81+ );
82+}
83+
84+#[test]
85+fn long_word_softwrap() {
86+ assert_eq!(
87+ softwrap_text("\t\txxxx1xxxx2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
88+ " xxxx1xxxx2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
89+ );
90+ assert_eq!(
91+ softwrap_text("xxxxxxxx1xxxx2xxx\n"),
92+ "xxxxxxxx1xxxx2xxx\n. \n "
93+ );
94+ assert_eq!(
95+ softwrap_text("\t\txxxx1xxxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
96+ " xxxx1xxxx \n.....2xxxx3xxxx4x\n.....xxx5xxxx6xxx\n.....x7xxxx8xxxx9\n.....xxx \n "
97+ );
98+ assert_eq!(
99+ softwrap_text("\t\txxxx1xxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
100+ " xxxx1xxx 2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
101+ );
102+}
103+
104+fn overlay_text(text: &str, char_pos: usize, softwrap: bool, overlays: &[Overlay]) -> String {
105+ DocumentFormatter::new_at_prev_checkpoint(
106+ text.into(),
107+ &TextFormat::new_test(softwrap),
108+ TextAnnotations::default().add_overlay(overlays.into(), None),
109+ char_pos,
110+ )
111+ .0
112+ .collect_to_str()
113+}
114+
115+#[test]
116+fn overlay() {
117+ assert_eq!(
118+ overlay_text(
119+ "foobar",
120+ 0,
121+ false,
122+ &[Overlay::new(0, "X"), Overlay::new(2, "\t")],
123+ ),
124+ "Xo bar "
125+ );
126+ assert_eq!(
127+ overlay_text(
128+ &"foo ".repeat(10),
129+ 0,
130+ true,
131+ &[
132+ Overlay::new(2, "\t"),
133+ Overlay::new(5, "\t"),
134+ Overlay::new(16, "X"),
135+ ]
136+ ),
137+ "fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
138+ );
139+}
140+
141+fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -> String {
142+ DocumentFormatter::new_at_prev_checkpoint(
143+ text.into(),
144+ &TextFormat::new_test(softwrap),
145+ TextAnnotations::default().add_inline_annotations(annotations.into(), None),
146+ 0,
147+ )
148+ .0
149+ .collect_to_str()
150+}
151+
152+#[test]
153+fn annotation() {
154+ assert_eq!(
155+ annotate_text("bar", false, &[InlineAnnotation::new(0, "foo")]),
156+ "foobar "
157+ );
158+ assert_eq!(
159+ annotate_text(
160+ &"foo ".repeat(10),
161+ true,
162+ &[InlineAnnotation::new(0, "foo ")]
163+ ),
164+ "foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
165+ );
166+}
167+#[test]
168+fn annotation_and_overlay() {
169+ assert_eq!(
170+ DocumentFormatter::new_at_prev_checkpoint(
171+ "bbar".into(),
172+ &TextFormat::new_test(false),
173+ TextAnnotations::default()
174+ .add_inline_annotations(Rc::new([InlineAnnotation::new(0, "fooo")]), None)
175+ .add_overlay(Rc::new([Overlay::new(0, "\t")]), None),
176+ 0,
177+ )
178+ .0
179+ .collect_to_str(),
180+ "fooo bar "
181+ );
182+}
helix-core/src/fuzzy.rsnew file+43
@@ -0,0 +1,43 @@
1+use std::ops::DerefMut;
2+
3+use nucleo::pattern::{Atom, AtomKind, CaseMatching};
4+use nucleo::Config;
5+use parking_lot::Mutex;
6+
7+pub struct LazyMutex<T> {
8+ inner: Mutex<Option<T>>,
9+ init: fn() -> T,
10+}
11+
12+impl<T> LazyMutex<T> {
13+ pub const fn new(init: fn() -> T) -> Self {
14+ Self {
15+ inner: Mutex::new(None),
16+ init,
17+ }
18+ }
19+
20+ pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
21+ parking_lot::MutexGuard::map(self.inner.lock(), |val| val.get_or_insert_with(self.init))
22+ }
23+}
24+
25+pub static MATCHER: LazyMutex<nucleo::Matcher> = LazyMutex::new(nucleo::Matcher::default);
26+
27+/// convenience function to easily fuzzy match
28+/// on a (relatively small list of inputs). This is not recommended for building a full tui
29+/// application that can match large numbers of matches as all matching is done on the current
30+/// thread, effectively blocking the UI
31+pub fn fuzzy_match<T: AsRef<str>>(
32+ pattern: &str,
33+ items: impl IntoIterator<Item = T>,
34+ path: bool,
35+) -> Vec<(T, u16)> {
36+ let mut matcher = MATCHER.lock();
37+ matcher.config = Config::DEFAULT;
38+ if path {
39+ matcher.config.set_match_paths();
40+ }
41+ let pattern = Atom::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy, false);
42+ pattern.match_list(items, &mut matcher)
43+}
helix-core/src/graphemes.rs+379−68
@@ -1,15 +1,101 @@
1+//! Utility functions to traverse the unicode graphemes of a `Rope`'s text contents.
2+//!
1−// Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs
3+//! Based on <https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs>
2use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
4use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
3use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
5use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
4use unicode_width::UnicodeWidthStr;
6use unicode_width::UnicodeWidthStr;
5
7
8+use std::borrow::Cow;
9+use std::fmt::{self, Debug, Display};
10+use std::marker::PhantomData;
11+use std::ops::Deref;
12+use std::ptr::NonNull;
13+use std::{slice, str};
14+
15+use crate::chars::{char_is_whitespace, char_is_word};
16+use crate::LineEnding;
17+
18+#[inline]
19+pub fn tab_width_at(visual_x: usize, tab_width: u16) -> usize {
20+ tab_width as usize - (visual_x % tab_width as usize)
21+}
22+
23+#[derive(Debug, Clone, PartialEq, Eq)]
24+pub enum Grapheme<'a> {
25+ Newline,
26+ Tab { width: usize },
27+ Other { g: GraphemeStr<'a> },
28+}
29+
30+impl<'a> Grapheme<'a> {
31+ pub fn new(g: GraphemeStr<'a>, visual_x: usize, tab_width: u16) -> Grapheme<'a> {
32+ match g {
33+ g if g == "\t" => Grapheme::Tab {
34+ width: tab_width_at(visual_x, tab_width),
35+ },
36+ _ if LineEnding::from_str(&g).is_some() => Grapheme::Newline,
37+ _ => Grapheme::Other { g },
38+ }
39+ }
40+
41+ pub fn change_position(&mut self, visual_x: usize, tab_width: u16) {
42+ if let Grapheme::Tab { width } = self {
43+ *width = tab_width_at(visual_x, tab_width)
44+ }
45+ }
46+
47+ /// Returns the a visual width of this grapheme,
48+ #[inline]
49+ pub fn width(&self) -> usize {
50+ match *self {
51+ // width is not cached because we are dealing with
52+ // ASCII almost all the time which already has a fastpath
53+ // it's okay to convert to u16 here because no codepoint has a width larger
54+ // than 2 and graphemes are usually atmost two visible codepoints wide
55+ Grapheme::Other { ref g } => grapheme_width(g),
56+ Grapheme::Tab { width } => width,
57+ Grapheme::Newline => 1,
58+ }
59+ }
60+
61+ pub fn is_whitespace(&self) -> bool {
62+ !matches!(&self, Grapheme::Other { g } if !g.chars().all(char_is_whitespace))
63+ }
64+
65+ // TODO currently word boundaries are used for softwrapping.
66+ // This works best for programming languages and well for prose.
67+ // This could however be improved in the future by considering unicode
68+ // character classes but
69+ pub fn is_word_boundary(&self) -> bool {
70+ !matches!(&self, Grapheme::Other { g,.. } if g.chars().all(char_is_word))
71+ }
72+}
73+
74+impl Display for Grapheme<'_> {
75+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76+ match *self {
77+ Grapheme::Newline => write!(f, " "),
78+ Grapheme::Tab { width } => {
79+ for _ in 0..width {
80+ write!(f, " ")?;
81+ }
82+ Ok(())
83+ }
84+ Grapheme::Other { ref g } => {
85+ write!(f, "{g}")
86+ }
87+ }
88+ }
89+}
90+
91+#[must_use]
6pub fn grapheme_width(g: &str) -> usize {
92pub fn grapheme_width(g: &str) -> usize {
7 if g.as_bytes()[0] <= 127 {
93 if g.as_bytes()[0] <= 127 {
8 // Fast-path ascii.
94 // Fast-path ascii.
9 // Point 1: theoretically, ascii control characters should have zero
95 // Point 1: theoretically, ascii control characters should have zero
10 // width, but in our case we actually want them to have width: if they
96 // width, but in our case we actually want them to have width: if they
11 // show up in text, we want to treat them as textual elements that can
97 // show up in text, we want to treat them as textual elements that can
12− // be editied. So we can get away with making all ascii single width
98+ // be edited. So we can get away with making all ascii single width
13 // here.
99 // here.
14 // Point 2: we're only examining the first codepoint here, which means
100 // Point 2: we're only examining the first codepoint here, which means
15 // we're ignoring graphemes formed with combining characters. However,
101 // we're ignoring graphemes formed with combining characters. However,
@@ -22,28 +108,67 @@ pub fn grapheme_width(g: &str) -> usize {
22 // We use max(1) here because all grapeheme clusters--even illformed
108 // We use max(1) here because all grapeheme clusters--even illformed
23 // ones--should have at least some width so they can be edited
109 // ones--should have at least some width so they can be edited
24 // properly.
110 // properly.
111+ // TODO properly handle unicode width for all codepoints
112+ // example of where unicode width is currently wrong: 🤦🏼‍♂️ (taken from https://hsivonen.fi/string-length/)
25 UnicodeWidthStr::width(g).max(1)
113 UnicodeWidthStr::width(g).max(1)
26 }
114 }
27}
115}
28
116
117+#[must_use]
29−pub fn nth_prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize, n: usize) -> usize {
118+pub fn nth_prev_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -> usize {
30− // TODO: implement this more efficiently. This has to do a lot of
119+ // Bounds check
31− // re-scanning of rope chunks. Probably move the main implementation here,
120+ debug_assert!(char_idx <= slice.len_chars());
32− // and have prev_grapheme_boundary call this instead.
121+
33− let mut char_idx = char_idx;
122+ // We work with bytes for this, so convert.
123+ let mut byte_idx = slice.char_to_byte(char_idx);
124+
125+ // Get the chunk with our byte index in it.
126+ let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
127+
128+ // Set up the grapheme cursor.
129+ let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
130+
131+ // Find the previous grapheme cluster boundary.
34 for _ in 0..n {
132 for _ in 0..n {
35− char_idx = prev_grapheme_boundary(slice, char_idx);
133+ loop {
134+ match gc.prev_boundary(chunk, chunk_byte_idx) {
135+ Ok(None) => return 0,
136+ Ok(Some(n)) => {
137+ byte_idx = n;
138+ break;
139+ }
140+ Err(GraphemeIncomplete::PrevChunk) => {
141+ let (a, b, c, _) = slice.chunk_at_byte(chunk_byte_idx - 1);
142+ chunk = a;
143+ chunk_byte_idx = b;
144+ chunk_char_idx = c;
145+ }
146+ Err(GraphemeIncomplete::PreContext(n)) => {
147+ let ctx_chunk = slice.chunk_at_byte(n - 1).0;
148+ gc.provide_context(ctx_chunk, n - ctx_chunk.len());
149+ }
150+ _ => unreachable!(),
151+ }
152+ }
36 }
153 }
37− char_idx
154+ let tmp = byte_to_char_idx(chunk, byte_idx - chunk_byte_idx);
155+ chunk_char_idx + tmp
38}
156}
39
157
40/// Finds the previous grapheme boundary before the given char position.
158/// Finds the previous grapheme boundary before the given char position.
159+#[must_use]
160+#[inline(always)]
41−pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
161+pub fn prev_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
162+ nth_prev_grapheme_boundary(slice, char_idx, 1)
163+}
164+
165+#[must_use]
166+pub fn nth_next_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -> usize {
42 // Bounds check
167 // Bounds check
43 debug_assert!(char_idx <= slice.len_chars());
168 debug_assert!(char_idx <= slice.len_chars());
44
169
45 // We work with bytes for this, so convert.
170 // We work with bytes for this, so convert.
46− let byte_idx = slice.char_to_byte(char_idx);
171+ let mut byte_idx = slice.char_to_byte(char_idx);
47
172
48 // Get the chunk with our byte index in it.
173 // Get the chunk with our byte index in it.
49 let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
174 let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
@@ -51,42 +176,128 @@ pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
51 // Set up the grapheme cursor.
176 // Set up the grapheme cursor.
52 let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
177 let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
53
178
54− // Find the previous grapheme cluster boundary.
179+ // Find the nth next grapheme cluster boundary.
55− loop {
180+ for _ in 0..n {
181+ loop {
56− match gc.prev_boundary(chunk, chunk_byte_idx) {
182+ match gc.next_boundary(chunk, chunk_byte_idx) {
57− Ok(None) => return 0,
183+ Ok(None) => return slice.len_chars(),
58− Ok(Some(n)) => {
184+ Ok(Some(n)) => {
59− let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx);
185+ byte_idx = n;
60− return chunk_char_idx + tmp;
186+ break;
61− }
187+ }
62− Err(GraphemeIncomplete::PrevChunk) => {
188+ Err(GraphemeIncomplete::NextChunk) => {
189+ chunk_byte_idx += chunk.len();
63− let (a, b, c, _) = slice.chunk_at_byte(chunk_byte_idx - 1);
190+ let (a, _, c, _) = slice.chunk_at_byte(chunk_byte_idx);
64− chunk = a;
191+ chunk = a;
65− chunk_byte_idx = b;
66− chunk_char_idx = c;
192+ chunk_char_idx = c;
67− }
193+ }
68− Err(GraphemeIncomplete::PreContext(n)) => {
194+ Err(GraphemeIncomplete::PreContext(n)) => {
69− let ctx_chunk = slice.chunk_at_byte(n - 1).0;
195+ let ctx_chunk = slice.chunk_at_byte(n - 1).0;
70− gc.provide_context(ctx_chunk, n - ctx_chunk.len());
196+ gc.provide_context(ctx_chunk, n - ctx_chunk.len());
197+ }
198+ _ => unreachable!(),
71 }
199 }
72− _ => unreachable!(),
73 }
200 }
74 }
201 }
202+ let tmp = byte_to_char_idx(chunk, byte_idx - chunk_byte_idx);
203+ chunk_char_idx + tmp
75}
204}
76
205
206+#[must_use]
77−pub fn nth_next_grapheme_boundary(slice: &RopeSlice, char_idx: usize, n: usize) -> usize {
207+pub fn nth_next_grapheme_boundary_byte(slice: RopeSlice, mut byte_idx: usize, n: usize) -> usize {
78− // TODO: implement this more efficiently. This has to do a lot of
208+ // Bounds check
79− // re-scanning of rope chunks. Probably move the main implementation here,
209+ debug_assert!(byte_idx <= slice.len_bytes());
80− // and have next_grapheme_boundary call this instead.
210+
81− let mut char_idx = char_idx;
211+ // Get the chunk with our byte index in it.
212+ let (mut chunk, mut chunk_byte_idx, mut _chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
213+
214+ // Set up the grapheme cursor.
215+ let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
216+
217+ // Find the nth next grapheme cluster boundary.
82 for _ in 0..n {
218 for _ in 0..n {
83− char_idx = next_grapheme_boundary(slice, char_idx);
219+ loop {
220+ match gc.next_boundary(chunk, chunk_byte_idx) {
221+ Ok(None) => return slice.len_bytes(),
222+ Ok(Some(n)) => {
223+ byte_idx = n;
224+ break;
225+ }
226+ Err(GraphemeIncomplete::NextChunk) => {
227+ chunk_byte_idx += chunk.len();
228+ let (a, _, _c, _) = slice.chunk_at_byte(chunk_byte_idx);
229+ chunk = a;
230+ // chunk_char_idx = c;
231+ }
232+ Err(GraphemeIncomplete::PreContext(n)) => {
233+ let ctx_chunk = slice.chunk_at_byte(n - 1).0;
234+ gc.provide_context(ctx_chunk, n - ctx_chunk.len());
235+ }
236+ _ => unreachable!(),
237+ }
238+ }
84 }
239 }
85− char_idx
240+ byte_idx
86}
241}
87
242
88/// Finds the next grapheme boundary after the given char position.
243/// Finds the next grapheme boundary after the given char position.
244+#[must_use]
245+#[inline(always)]
89−pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
246+pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
247+ nth_next_grapheme_boundary(slice, char_idx, 1)
248+}
249+
250+/// Finds the next grapheme boundary after the given byte position.
251+#[must_use]
252+#[inline(always)]
253+pub fn next_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> usize {
254+ nth_next_grapheme_boundary_byte(slice, byte_idx, 1)
255+}
256+
257+/// Returns the passed char index if it's already a grapheme boundary,
258+/// or the next grapheme boundary char index if not.
259+#[must_use]
260+#[inline]
261+pub fn ensure_grapheme_boundary_next(slice: RopeSlice, char_idx: usize) -> usize {
262+ if char_idx == 0 {
263+ char_idx
264+ } else {
265+ next_grapheme_boundary(slice, char_idx - 1)
266+ }
267+}
268+
269+/// Returns the passed char index if it's already a grapheme boundary,
270+/// or the prev grapheme boundary char index if not.
271+#[must_use]
272+#[inline]
273+pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize {
274+ if char_idx == slice.len_chars() {
275+ char_idx
276+ } else {
277+ prev_grapheme_boundary(slice, char_idx + 1)
278+ }
279+}
280+
281+/// Returns the passed byte index if it's already a grapheme boundary,
282+/// or the next grapheme boundary byte index if not.
283+#[must_use]
284+#[inline]
285+pub fn ensure_grapheme_boundary_next_byte(slice: RopeSlice, byte_idx: usize) -> usize {
286+ if byte_idx == 0 {
287+ byte_idx
288+ } else {
289+ // TODO: optimize so we're not constructing grapheme cursor twice
290+ if is_grapheme_boundary_byte(slice, byte_idx) {
291+ byte_idx
292+ } else {
293+ next_grapheme_boundary_byte(slice, byte_idx)
294+ }
295+ }
296+}
297+
298+/// Returns whether the given char position is a grapheme boundary.
299+#[must_use]
300+pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
90 // Bounds check
301 // Bounds check
91 debug_assert!(char_idx <= slice.len_chars());
302 debug_assert!(char_idx <= slice.len_chars());
92
303
@@ -94,41 +305,29 @@ pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
94 let byte_idx = slice.char_to_byte(char_idx);
305 let byte_idx = slice.char_to_byte(char_idx);
95
306
96 // Get the chunk with our byte index in it.
307 // Get the chunk with our byte index in it.
97− let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
308+ let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
98
309
99 // Set up the grapheme cursor.
310 // Set up the grapheme cursor.
100 let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
311 let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
101
312
102− // Find the next grapheme cluster boundary.
313+ // Determine if the given position is a grapheme cluster boundary.
103 loop {
314 loop {
104− match gc.next_boundary(chunk, chunk_byte_idx) {
315+ match gc.is_boundary(chunk, chunk_byte_idx) {
105− Ok(None) => return slice.len_chars(),
316+ Ok(n) => return n,
106− Ok(Some(n)) => {
107− let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx);
108− return chunk_char_idx + tmp;
109− }
110− Err(GraphemeIncomplete::NextChunk) => {
111− chunk_byte_idx += chunk.len();
112− let (a, _, c, _) = slice.chunk_at_byte(chunk_byte_idx);
113− chunk = a;
114− chunk_char_idx = c;
115− }
116 Err(GraphemeIncomplete::PreContext(n)) => {
317 Err(GraphemeIncomplete::PreContext(n)) => {
117− let ctx_chunk = slice.chunk_at_byte(n - 1).0;
318+ let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
118− gc.provide_context(ctx_chunk, n - ctx_chunk.len());
319+ gc.provide_context(ctx_chunk, ctx_byte_start);
119 }
320 }
120− _ => unreachable!(),
321+ Err(_) => unreachable!(),
121 }
322 }
122 }
323 }
123}
324}
124
325
125−/// Returns whether the given char position is a grapheme boundary.
326+/// Returns whether the given byte position is a grapheme boundary.
327+#[must_use]
126−pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool {
328+pub fn is_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> bool {
127 // Bounds check
329 // Bounds check
128− debug_assert!(char_idx <= slice.len_chars());
330+ debug_assert!(byte_idx <= slice.len_bytes());
129−
130− // We work with bytes for this, so convert.
131− let byte_idx = slice.char_to_byte(char_idx);
132
331
133 // Get the chunk with our byte index in it.
332 // Get the chunk with our byte index in it.
134 let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
333 let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
@@ -144,12 +343,12 @@ pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool {
144 let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
343 let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
145 gc.provide_context(ctx_chunk, ctx_byte_start);
344 gc.provide_context(ctx_chunk, ctx_byte_start);
146 }
345 }
147− _ => unreachable!(),
346+ Err(_) => unreachable!(),
148 }
347 }
149 }
348 }
150}
349}
151
350
152−/// An iterator over the graphemes of a RopeSlice.
351+/// An iterator over the graphemes of a `RopeSlice`.
153#[derive(Clone)]
352#[derive(Clone)]
154pub struct RopeGraphemes<'a> {
353pub struct RopeGraphemes<'a> {
155 text: RopeSlice<'a>,
354 text: RopeSlice<'a>,
@@ -159,12 +358,25 @@ pub struct RopeGraphemes<'a> {
159 cursor: GraphemeCursor,
358 cursor: GraphemeCursor,
160}
359}
161
360
361+impl<'a> fmt::Debug for RopeGraphemes<'a> {
362+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363+ f.debug_struct("RopeGraphemes")
364+ .field("text", &self.text)
365+ .field("chunks", &self.chunks)
366+ .field("cur_chunk", &self.cur_chunk)
367+ .field("cur_chunk_start", &self.cur_chunk_start)
368+ // .field("cursor", &self.cursor)
369+ .finish()
370+ }
371+}
372+
162impl<'a> RopeGraphemes<'a> {
373impl<'a> RopeGraphemes<'a> {
374+ #[must_use]
163− pub fn new<'b>(slice: &RopeSlice<'b>) -> RopeGraphemes<'b> {
375+ pub fn new(slice: RopeSlice) -> RopeGraphemes {
164 let mut chunks = slice.chunks();
376 let mut chunks = slice.chunks();
165 let first_chunk = chunks.next().unwrap_or("");
377 let first_chunk = chunks.next().unwrap_or("");
166 RopeGraphemes {
378 RopeGraphemes {
167− text: *slice,
379+ text: slice,
168 chunks,
380 chunks,
169 cur_chunk: first_chunk,
381 cur_chunk: first_chunk,
170 cur_chunk_start: 0,
382 cur_chunk_start: 0,
@@ -195,15 +407,16 @@ impl<'a> Iterator for RopeGraphemes<'a> {
195 self.cur_chunk_start += self.cur_chunk.len();
407 self.cur_chunk_start += self.cur_chunk.len();
196 self.cur_chunk = self.chunks.next().unwrap_or("");
408 self.cur_chunk = self.chunks.next().unwrap_or("");
197 }
409 }
410+ Err(GraphemeIncomplete::PreContext(idx)) => {
411+ let (chunk, byte_idx, _, _) = self.text.chunk_at_byte(idx.saturating_sub(1));
412+ self.cursor.provide_context(chunk, byte_idx);
413+ }
198 _ => unreachable!(),
414 _ => unreachable!(),
199 }
415 }
200 }
416 }
201
417
202 if a < self.cur_chunk_start {
418 if a < self.cur_chunk_start {
203− let a_char = self.text.byte_to_char(a);
419+ Some(self.text.byte_slice(a..b))
204− let b_char = self.text.byte_to_char(b);
205−
206− Some(self.text.slice(a_char..b_char))
207 } else {
420 } else {
208 let a2 = a - self.cur_chunk_start;
421 let a2 = a - self.cur_chunk_start;
209 let b2 = b - self.cur_chunk_start;
422 let b2 = b - self.cur_chunk_start;
@@ -211,3 +424,101 @@ impl<'a> Iterator for RopeGraphemes<'a> {
211 }
424 }
212 }
425 }
213}
426}
427+
428+/// A highly compressed Cow<'a, str> that holds
429+/// atmost u31::MAX bytes and is readonly
430+pub struct GraphemeStr<'a> {
431+ ptr: NonNull<u8>,
432+ len: u32,
433+ phantom: PhantomData<&'a str>,
434+}
435+
436+impl GraphemeStr<'_> {
437+ const MASK_OWNED: u32 = 1 << 31;
438+
439+ fn compute_len(&self) -> usize {
440+ (self.len & !Self::MASK_OWNED) as usize
441+ }
442+}
443+
444+impl Deref for GraphemeStr<'_> {
445+ type Target = str;
446+ fn deref(&self) -> &Self::Target {
447+ unsafe {
448+ let bytes = slice::from_raw_parts(self.ptr.as_ptr(), self.compute_len());
449+ str::from_utf8_unchecked(bytes)
450+ }
451+ }
452+}
453+
454+impl Drop for GraphemeStr<'_> {
455+ fn drop(&mut self) {
456+ if self.len & Self::MASK_OWNED != 0 {
457+ // free allocation
458+ unsafe {
459+ drop(Box::from_raw(slice::from_raw_parts_mut(
460+ self.ptr.as_ptr(),
461+ self.compute_len(),
462+ )));
463+ }
464+ }
465+ }
466+}
467+
468+impl<'a> From<&'a str> for GraphemeStr<'a> {
469+ fn from(g: &'a str) -> Self {
470+ GraphemeStr {
471+ ptr: unsafe { NonNull::new_unchecked(g.as_bytes().as_ptr() as *mut u8) },
472+ len: i32::try_from(g.len()).unwrap() as u32,
473+ phantom: PhantomData,
474+ }
475+ }
476+}
477+
478+impl<'a> From<String> for GraphemeStr<'a> {
479+ fn from(g: String) -> Self {
480+ let len = g.len();
481+ let ptr = Box::into_raw(g.into_bytes().into_boxed_slice()) as *mut u8;
482+ GraphemeStr {
483+ ptr: unsafe { NonNull::new_unchecked(ptr) },
484+ len: (i32::try_from(len).unwrap() as u32) | Self::MASK_OWNED,
485+ phantom: PhantomData,
486+ }
487+ }
488+}
489+
490+impl<'a> From<Cow<'a, str>> for GraphemeStr<'a> {
491+ fn from(g: Cow<'a, str>) -> Self {
492+ match g {
493+ Cow::Borrowed(g) => g.into(),
494+ Cow::Owned(g) => g.into(),
495+ }
496+ }
497+}
498+
499+impl<T: Deref<Target = str>> PartialEq<T> for GraphemeStr<'_> {
500+ fn eq(&self, other: &T) -> bool {
501+ self.deref() == other.deref()
502+ }
503+}
504+impl PartialEq<str> for GraphemeStr<'_> {
505+ fn eq(&self, other: &str) -> bool {
506+ self.deref() == other
507+ }
508+}
509+impl Eq for GraphemeStr<'_> {}
510+impl Debug for GraphemeStr<'_> {
511+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512+ Debug::fmt(self.deref(), f)
513+ }
514+}
515+impl Display for GraphemeStr<'_> {
516+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517+ Display::fmt(self.deref(), f)
518+ }
519+}
520+impl Clone for GraphemeStr<'_> {
521+ fn clone(&self) -> Self {
522+ self.deref().to_owned().into()
523+ }
524+}
helix-core/src/history.rs+547−54
@@ -1,19 +1,68 @@
1−use crate::{ChangeSet, Rope, State, Transaction};
1+use crate::{Assoc, ChangeSet, Range, Rope, Selection, Transaction};
2−use smallvec::{smallvec, SmallVec};
2+use once_cell::sync::Lazy;
3+use regex::Regex;
4+use std::num::NonZeroUsize;
5+use std::time::{Duration, Instant};
3
6
4−/// Undo-tree style history store.
7+#[derive(Debug, Clone)]
8+pub struct State {
9+ pub doc: Rope,
10+ pub selection: Selection,
11+}
12+
13+/// Stores the history of changes to a buffer.
14+///
15+/// Currently the history is represented as a vector of revisions. The vector
16+/// always has at least one element: the empty root revision. Each revision
17+/// with the exception of the root has a parent revision, a [Transaction]
18+/// that can be applied to its parent to transition from the parent to itself,
19+/// and an inversion of that transaction to transition from the parent to its
20+/// latest child.
21+///
22+/// When using `u` to undo a change, an inverse of the stored transaction will
23+/// be applied which will transition the buffer to the parent state.
24+///
25+/// Each revision with the exception of the last in the vector also has a
26+/// last child revision. When using `U` to redo a change, the last child transaction
27+/// will be applied to the current state of the buffer.
28+///
29+/// The current revision is the one currently displayed in the buffer.
30+///
31+/// Committing a new revision to the history will update the last child of the
32+/// current revision, and push a new revision to the end of the vector.
33+///
34+/// Revisions are committed with a timestamp. :earlier and :later can be used
35+/// to jump to the closest revision to a moment in time relative to the timestamp
36+/// of the current revision plus (:later) or minus (:earlier) the duration
37+/// given to the command. If a single integer is given, the editor will instead
38+/// jump the given number of revisions in the vector.
39+///
40+/// Limitations:
41+/// * Changes in selections currently don't commit history changes. The selection
42+/// will only be updated to the state after a committed buffer change.
43+/// * The vector of history revisions is currently unbounded. This might
44+/// cause the memory consumption to grow significantly large during long
45+/// editing sessions.
46+/// * Because delete transactions currently don't store the text that they
47+/// delete, we also store an inversion of the transaction.
48+///
49+/// Using time to navigate the history: <https://github.com/helix-editor/helix/pull/194>
50+#[derive(Debug)]
5pub struct History {
51pub struct History {
6 revisions: Vec<Revision>,
52 revisions: Vec<Revision>,
7− cursor: usize,
53+ current: usize,
8}
54}
9
55
56+/// A single point in history. See [History] for more information.
10−#[derive(Debug)]
57+#[derive(Debug, Clone)]
11struct Revision {
58struct Revision {
12 parent: usize,
59 parent: usize,
13− children: SmallVec<[(usize, Transaction); 1]>,
60+ last_child: Option<NonZeroUsize>,
14− /// The transaction to revert to previous state.
61+ transaction: Transaction,
62+ // We need an inversion for undos because delete transactions don't store
63+ // the deleted text.
15− revert: Transaction,
64+ inversion: Transaction,
16− // selection before, selection after?
65+ timestamp: Instant,
17}
66}
18
67
19impl Default for History {
68impl Default for History {
@@ -22,119 +71,563 @@ impl Default for History {
22 Self {
71 Self {
23 revisions: vec![Revision {
72 revisions: vec![Revision {
24 parent: 0,
73 parent: 0,
25− children: SmallVec::new(),
74+ last_child: None,
75+ transaction: Transaction::from(ChangeSet::new("".into())),
26− revert: Transaction::from(ChangeSet::new(&Rope::new())),
76+ inversion: Transaction::from(ChangeSet::new("".into())),
77+ timestamp: Instant::now(),
27 }],
78 }],
28− cursor: 0,
79+ current: 0,
29 }
80 }
30 }
81 }
31}
82}
32
83
33impl History {
84impl History {
34 pub fn commit_revision(&mut self, transaction: &Transaction, original: &State) {
85 pub fn commit_revision(&mut self, transaction: &Transaction, original: &State) {
35− // TODO: could store a single transaction, if deletes also stored the text they delete
86+ self.commit_revision_at_timestamp(transaction, original, Instant::now());
87+ }
88+
89+ pub fn commit_revision_at_timestamp(
90+ &mut self,
91+ transaction: &Transaction,
92+ original: &State,
93+ timestamp: Instant,
94+ ) {
95+ let inversion = transaction
36− let revert = transaction.invert(original);
96+ .invert(&original.doc)
97+ // Store the current cursor position
98+ .with_selection(original.selection.clone());
37
99
38− let new_cursor = self.revisions.len();
100+ let new_current = self.revisions.len();
101+ self.revisions[self.current].last_child = NonZeroUsize::new(new_current);
39 self.revisions.push(Revision {
102 self.revisions.push(Revision {
40− parent: self.cursor,
103+ parent: self.current,
41− children: SmallVec::new(),
104+ last_child: None,
42− revert,
105+ transaction: transaction.clone(),
106+ inversion,
107+ timestamp,
43 });
108 });
109+ self.current = new_current;
110+ }
44
111
45− // add a reference to the parent
112+ #[inline]
46− self.revisions
113+ pub fn current_revision(&self) -> usize {
47− .get_mut(self.cursor)
114+ self.current
48− .unwrap() // TODO: get_unchecked_mut
49− .children
50− .push((new_cursor, transaction.clone()));
51−
52− self.cursor = new_cursor;
53 }
115 }
54
116
55 #[inline]
117 #[inline]
56− pub fn at_root(&self) -> bool {
118+ pub const fn at_root(&self) -> bool {
57− self.cursor == 0
119+ self.current == 0
58 }
120 }
59
121
60− // TODO: I'd like to pass Transaction by reference but it fights with the borrowck
122+ /// Returns the changes since the given revision composed into a transaction.
123+ /// Returns None if there are no changes between the current and given revisions.
124+ pub fn changes_since(&self, revision: usize) -> Option<Transaction> {
125+ let lca = self.lowest_common_ancestor(revision, self.current);
126+ let up = self.path_up(revision, lca);
127+ let down = self.path_up(self.current, lca);
128+ let up_txns = up
129+ .iter()
130+ .rev()
131+ .map(|&n| self.revisions[n].inversion.clone());
132+ let down_txns = down.iter().map(|&n| self.revisions[n].transaction.clone());
133+
134+ down_txns.chain(up_txns).reduce(|acc, tx| tx.compose(acc))
135+ }
61
136
137+ /// Undo the last edit.
62− pub fn undo(&mut self) -> Option<Transaction> {
138+ pub fn undo(&mut self) -> Option<&Transaction> {
63 if self.at_root() {
139 if self.at_root() {
64− // We're at the root of undo, nothing to do.
65 return None;
140 return None;
66 }
141 }
67
142
68− let current_revision = &self.revisions[self.cursor];
143+ let current_revision = &self.revisions[self.current];
144+ self.current = current_revision.parent;
145+ Some(&current_revision.inversion)
146+ }
147+
148+ /// Redo the last edit.
149+ pub fn redo(&mut self) -> Option<&Transaction> {
150+ let current_revision = &self.revisions[self.current];
151+ let last_child = current_revision.last_child?;
152+ self.current = last_child.get();
153+
154+ Some(&self.revisions[last_child.get()].transaction)
155+ }
156+
157+ // Get the position of last change
158+ pub fn last_edit_pos(&self) -> Option<usize> {
159+ if self.current == 0 {
160+ return None;
161+ }
162+ let current_revision = &self.revisions[self.current];
163+ let primary_selection = current_revision
164+ .inversion
165+ .selection()
166+ .expect("inversion always contains a selection")
167+ .primary();
168+ let (_from, to, _fragment) = current_revision
169+ .transaction
170+ .changes_iter()
171+ // find a change that matches the primary selection
172+ .find(|(from, to, _fragment)| Range::new(*from, *to).overlaps(&primary_selection))
173+ // or use the first change
174+ .or_else(|| current_revision.transaction.changes_iter().next())
175+ .unwrap();
176+ let pos = current_revision
177+ .transaction
178+ .changes()
179+ .map_pos(to, Assoc::After);
180+ Some(pos)
181+ }
182+
183+ fn lowest_common_ancestor(&self, mut a: usize, mut b: usize) -> usize {
184+ use std::collections::HashSet;
185+ let mut a_path_set = HashSet::new();
186+ let mut b_path_set = HashSet::new();
187+ loop {
188+ a_path_set.insert(a);
189+ b_path_set.insert(b);
190+ if a_path_set.contains(&b) {
191+ return b;
192+ }
193+ if b_path_set.contains(&a) {
194+ return a;
195+ }
196+ a = self.revisions[a].parent; // Relies on the parent of 0 being 0.
197+ b = self.revisions[b].parent; // Same as above.
198+ }
199+ }
200+
201+ /// List of nodes on the way from `n` to 'a`. Doesn't include `a`.
202+ /// Includes `n` unless `a == n`. `a` must be an ancestor of `n`.
203+ fn path_up(&self, mut n: usize, a: usize) -> Vec<usize> {
204+ let mut path = Vec::new();
205+ while n != a {
206+ path.push(n);
207+ n = self.revisions[n].parent;
208+ }
209+ path
210+ }
211+
212+ /// Create a [`Transaction`] that will jump to a specific revision in the history.
213+ fn jump_to(&mut self, to: usize) -> Vec<Transaction> {
214+ let lca = self.lowest_common_ancestor(self.current, to);
215+ let up = self.path_up(self.current, lca);
216+ let down = self.path_up(to, lca);
217+ self.current = to;
218+ let up_txns = up.iter().map(|&n| self.revisions[n].inversion.clone());
219+ let down_txns = down
220+ .iter()
221+ .rev()
222+ .map(|&n| self.revisions[n].transaction.clone());
223+ up_txns.chain(down_txns).collect()
224+ }
225+
226+ /// Creates a [`Transaction`] that will undo `delta` revisions.
227+ fn jump_backward(&mut self, delta: usize) -> Vec<Transaction> {
228+ self.jump_to(self.current.saturating_sub(delta))
229+ }
230+
231+ /// Creates a [`Transaction`] that will redo `delta` revisions.
232+ fn jump_forward(&mut self, delta: usize) -> Vec<Transaction> {
233+ self.jump_to(
234+ self.current
235+ .saturating_add(delta)
236+ .min(self.revisions.len() - 1),
237+ )
238+ }
239+
240+ /// Helper for a binary search case below.
241+ fn revision_closer_to_instant(&self, i: usize, instant: Instant) -> usize {
242+ let dur_im1 = instant.duration_since(self.revisions[i - 1].timestamp);
243+ let dur_i = self.revisions[i].timestamp.duration_since(instant);
244+ use std::cmp::Ordering::*;
245+ match dur_im1.cmp(&dur_i) {
246+ Less => i - 1,
247+ Equal | Greater => i,
248+ }
249+ }
250+
251+ /// Creates a [`Transaction`] that will match a revision created at around
252+ /// `instant`.
253+ fn jump_instant(&mut self, instant: Instant) -> Vec<Transaction> {
254+ let search_result = self
255+ .revisions
256+ .binary_search_by(|rev| rev.timestamp.cmp(&instant));
257+ let revision = match search_result {
258+ Ok(revision) => revision,
259+ Err(insert_point) => match insert_point {
260+ 0 => 0,
261+ n if n == self.revisions.len() => n - 1,
262+ i => self.revision_closer_to_instant(i, instant),
263+ },
264+ };
265+ self.jump_to(revision)
266+ }
267+
268+ /// Creates a [`Transaction`] that will match a revision created `duration` ago
269+ /// from the timestamp of current revision.
270+ fn jump_duration_backward(&mut self, duration: Duration) -> Vec<Transaction> {
271+ match self.revisions[self.current].timestamp.checked_sub(duration) {
272+ Some(instant) => self.jump_instant(instant),
273+ None => self.jump_to(0),
274+ }
275+ }
276+
277+ /// Creates a [`Transaction`] that will match a revision created `duration` in
278+ /// the future from the timestamp of the current revision.
279+ fn jump_duration_forward(&mut self, duration: Duration) -> Vec<Transaction> {
280+ match self.revisions[self.current].timestamp.checked_add(duration) {
281+ Some(instant) => self.jump_instant(instant),
282+ None => self.jump_to(self.revisions.len() - 1),
283+ }
284+ }
285+
286+ /// Creates an undo [`Transaction`].
287+ pub fn earlier(&mut self, uk: UndoKind) -> Vec<Transaction> {
288+ use UndoKind::*;
289+ match uk {
290+ Steps(n) => self.jump_backward(n),
291+ TimePeriod(d) => self.jump_duration_backward(d),
292+ }
293+ }
294+
295+ /// Creates a redo [`Transaction`].
296+ pub fn later(&mut self, uk: UndoKind) -> Vec<Transaction> {
297+ use UndoKind::*;
298+ match uk {
299+ Steps(n) => self.jump_forward(n),
300+ TimePeriod(d) => self.jump_duration_forward(d),
301+ }
302+ }
303+}
304+
305+/// Whether to undo by a number of edits or a duration of time.
306+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
307+pub enum UndoKind {
308+ Steps(usize),
309+ TimePeriod(std::time::Duration),
310+}
311+
312+/// A subset of systemd.time time span syntax units.
313+const TIME_UNITS: &[(&[&str], &str, u64)] = &[
314+ (&["seconds", "second", "sec", "s"], "seconds", 1),
315+ (&["minutes", "minute", "min", "m"], "minutes", 60),
316+ (&["hours", "hour", "hr", "h"], "hours", 60 * 60),
317+ (&["days", "day", "d"], "days", 24 * 60 * 60),
318+];
319+
320+/// Checks if the duration input can be turned into a valid duration. It must be a
321+/// positive integer and denote the [unit of time.](`TIME_UNITS`)
322+/// Examples of valid durations:
323+/// * `5 sec`
324+/// * `5 min`
325+/// * `5 hr`
326+/// * `5 days`
327+static DURATION_VALIDATION_REGEX: Lazy<Regex> =
328+ Lazy::new(|| Regex::new(r"^(?:\d+\s*[a-z]+\s*)+$").unwrap());
329+
330+/// Captures both the number and unit as separate capture groups.
331+static NUMBER_UNIT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d+)\s*([a-z]+)").unwrap());
332+
333+/// Parse a string (e.g. "5 sec") and try to convert it into a [`Duration`].
334+fn parse_human_duration(s: &str) -> Result<Duration, String> {
335+ if !DURATION_VALIDATION_REGEX.is_match(s) {
336+ return Err("duration should be composed \
337+ of positive integers followed by time units"
338+ .to_string());
339+ }
340+
341+ let mut specified = [false; TIME_UNITS.len()];
342+ let mut seconds = 0u64;
343+ for cap in NUMBER_UNIT_REGEX.captures_iter(s) {
344+ let (n, unit_str) = (&cap[1], &cap[2]);
345+
346+ let n: u64 = n.parse().map_err(|_| format!("integer too large: {}", n))?;
69
347
70− self.cursor = current_revision.parent;
348+ let time_unit = TIME_UNITS
349+ .iter()
350+ .enumerate()
351+ .find(|(_, (forms, _, _))| forms.iter().any(|f| f == &unit_str));
352+
353+ if let Some((i, (_, unit, mul))) = time_unit {
354+ if specified[i] {
355+ return Err(format!("{} specified more than once", unit));
356+ }
357+ specified[i] = true;
71
358
72− Some(current_revision.revert.clone())
359+ let new_seconds = n.checked_mul(*mul).and_then(|s| seconds.checked_add(s));
360+ match new_seconds {
361+ Some(ns) => seconds = ns,
362+ None => return Err("duration too large".to_string()),
363+ }
364+ } else {
365+ return Err(format!("incorrect time unit: {}", unit_str));
366+ }
73 }
367 }
74
368
75− pub fn redo(&mut self) -> Option<Transaction> {
369+ Ok(Duration::from_secs(seconds))
76− let current_revision = &self.revisions[self.cursor];
370+}
77
371
78− // for now, simply pick the latest child (linear undo / redo)
372+impl std::str::FromStr for UndoKind {
79− if let Some((index, transaction)) = current_revision.children.last() {
373+ type Err = String;
80− self.cursor = *index;
81
374
82− return Some(transaction.clone());
375+ fn from_str(s: &str) -> Result<Self, Self::Err> {
376+ let s = s.trim();
377+ if s.is_empty() {
378+ Ok(Self::Steps(1usize))
379+ } else if let Ok(n) = s.parse::<usize>() {
380+ Ok(UndoKind::Steps(n))
381+ } else {
382+ Ok(Self::TimePeriod(parse_human_duration(s)?))
83 }
383 }
84− None
85 }
384 }
86}
385}
87
386
88#[cfg(test)]
387#[cfg(test)]
89mod test {
388mod test {
90 use super::*;
389 use super::*;
390+ use crate::Selection;
91
391
92 #[test]
392 #[test]
93 fn test_undo_redo() {
393 fn test_undo_redo() {
94 let mut history = History::default();
394 let mut history = History::default();
95 let doc = Rope::from("hello");
395 let doc = Rope::from("hello");
96− let mut state = State::new(doc);
396+ let mut state = State {
397+ doc,
398+ selection: Selection::point(0),
399+ };
97
400
98 let transaction1 =
401 let transaction1 =
99− Transaction::change(&state, vec![(5, 5, Some(" world!".into()))].into_iter());
402+ Transaction::change(&state.doc, vec![(5, 5, Some(" world!".into()))].into_iter());
100
403
101 // Need to commit before applying!
404 // Need to commit before applying!
102 history.commit_revision(&transaction1, &state);
405 history.commit_revision(&transaction1, &state);
103− transaction1.apply(&mut state);
406+ transaction1.apply(&mut state.doc);
104− assert_eq!("hello world!", state.doc());
407+ assert_eq!("hello world!", state.doc);
105
408
106 // ---
409 // ---
107
410
108 let transaction2 =
411 let transaction2 =
109− Transaction::change(&state, vec![(6, 11, Some("世界".into()))].into_iter());
412+ Transaction::change(&state.doc, vec![(6, 11, Some("世界".into()))].into_iter());
110
413
111 // Need to commit before applying!
414 // Need to commit before applying!
112 history.commit_revision(&transaction2, &state);
415 history.commit_revision(&transaction2, &state);
113− transaction2.apply(&mut state);
416+ transaction2.apply(&mut state.doc);
114− assert_eq!("hello 世界!", state.doc());
417+ assert_eq!("hello 世界!", state.doc);
115
418
116 // ---
419 // ---
117 fn undo(history: &mut History, state: &mut State) {
420 fn undo(history: &mut History, state: &mut State) {
118 if let Some(transaction) = history.undo() {
421 if let Some(transaction) = history.undo() {
119− transaction.apply(state);
422+ transaction.apply(&mut state.doc);
120 }
423 }
121 }
424 }
122 fn redo(history: &mut History, state: &mut State) {
425 fn redo(history: &mut History, state: &mut State) {
123 if let Some(transaction) = history.redo() {
426 if let Some(transaction) = history.redo() {
124− transaction.apply(state);
427+ transaction.apply(&mut state.doc);
125 }
428 }
126 }
429 }
127
430
128 undo(&mut history, &mut state);
431 undo(&mut history, &mut state);
129− assert_eq!("hello world!", state.doc());
432+ assert_eq!("hello world!", state.doc);
130 redo(&mut history, &mut state);
433 redo(&mut history, &mut state);
131− assert_eq!("hello 世界!", state.doc());
434+ assert_eq!("hello 世界!", state.doc);
132 undo(&mut history, &mut state);
435 undo(&mut history, &mut state);
133 undo(&mut history, &mut state);
436 undo(&mut history, &mut state);
134− assert_eq!("hello", state.doc());
437+ assert_eq!("hello", state.doc);
135
438
136 // undo at root is a no-op
439 // undo at root is a no-op
137 undo(&mut history, &mut state);
440 undo(&mut history, &mut state);
138− assert_eq!("hello", state.doc());
441+ assert_eq!("hello", state.doc);
442+ }
443+
444+ #[test]
445+ fn test_earlier_later() {
446+ let mut history = History::default();
447+ let doc = Rope::from("a\n");
448+ let mut state = State {
449+ doc,
450+ selection: Selection::point(0),
451+ };
452+
453+ fn undo(history: &mut History, state: &mut State) {
454+ if let Some(transaction) = history.undo() {
455+ transaction.apply(&mut state.doc);
456+ }
457+ }
458+
459+ fn earlier(history: &mut History, state: &mut State, uk: UndoKind) {
460+ let txns = history.earlier(uk);
461+ for txn in txns {
462+ txn.apply(&mut state.doc);
463+ }
464+ }
465+
466+ fn later(history: &mut History, state: &mut State, uk: UndoKind) {
467+ let txns = history.later(uk);
468+ for txn in txns {
469+ txn.apply(&mut state.doc);
470+ }
471+ }
472+
473+ fn commit_change(
474+ history: &mut History,
475+ state: &mut State,
476+ change: crate::transaction::Change,
477+ instant: Instant,
478+ ) {
479+ let txn = Transaction::change(&state.doc, vec![change].into_iter());
480+ history.commit_revision_at_timestamp(&txn, state, instant);
481+ txn.apply(&mut state.doc);
482+ }
483+
484+ let t0 = Instant::now();
485+ let t = |n| t0.checked_add(Duration::from_secs(n)).unwrap();
486+
487+ commit_change(&mut history, &mut state, (1, 1, Some(" b".into())), t(0));
488+ assert_eq!("a b\n", state.doc);
489+
490+ commit_change(&mut history, &mut state, (3, 3, Some(" c".into())), t(10));
491+ assert_eq!("a b c\n", state.doc);
492+
493+ commit_change(&mut history, &mut state, (5, 5, Some(" d".into())), t(20));
494+ assert_eq!("a b c d\n", state.doc);
495+
496+ undo(&mut history, &mut state);
497+ assert_eq!("a b c\n", state.doc);
498+
499+ commit_change(&mut history, &mut state, (5, 5, Some(" e".into())), t(30));
500+ assert_eq!("a b c e\n", state.doc);
501+
502+ undo(&mut history, &mut state);
503+ undo(&mut history, &mut state);
504+ assert_eq!("a b\n", state.doc);
505+
506+ commit_change(&mut history, &mut state, (1, 3, None), t(40));
507+ assert_eq!("a\n", state.doc);
508+
509+ commit_change(&mut history, &mut state, (1, 1, Some(" f".into())), t(50));
510+ assert_eq!("a f\n", state.doc);
511+
512+ use UndoKind::*;
513+
514+ earlier(&mut history, &mut state, Steps(3));
515+ assert_eq!("a b c d\n", state.doc);
516+
517+ later(&mut history, &mut state, TimePeriod(Duration::new(20, 0)));
518+ assert_eq!("a\n", state.doc);
519+
520+ earlier(&mut history, &mut state, TimePeriod(Duration::new(19, 0)));
521+ assert_eq!("a b c d\n", state.doc);
522+
523+ earlier(
524+ &mut history,
525+ &mut state,
526+ TimePeriod(Duration::new(10000, 0)),
527+ );
528+ assert_eq!("a\n", state.doc);
529+
530+ later(&mut history, &mut state, Steps(50));
531+ assert_eq!("a f\n", state.doc);
532+
533+ earlier(&mut history, &mut state, Steps(4));
534+ assert_eq!("a b c\n", state.doc);
535+
536+ later(&mut history, &mut state, TimePeriod(Duration::new(1, 0)));
537+ assert_eq!("a b c\n", state.doc);
538+
539+ later(&mut history, &mut state, TimePeriod(Duration::new(5, 0)));
540+ assert_eq!("a b c d\n", state.doc);
541+
542+ later(&mut history, &mut state, TimePeriod(Duration::new(6, 0)));
543+ assert_eq!("a b c e\n", state.doc);
544+
545+ later(&mut history, &mut state, Steps(1));
546+ assert_eq!("a\n", state.doc);
547+ }
548+
549+ #[test]
550+ fn test_parse_undo_kind() {
551+ use UndoKind::*;
552+
553+ // Default is one step.
554+ assert_eq!("".parse(), Ok(Steps(1)));
555+
556+ // An integer means the number of steps.
557+ assert_eq!("1".parse(), Ok(Steps(1)));
558+ assert_eq!(" 16 ".parse(), Ok(Steps(16)));
559+
560+ // Duration has a strict format.
561+ let validation_err = Err("duration should be composed \
562+ of positive integers followed by time units"
563+ .to_string());
564+ assert_eq!(" 16 33".parse::<UndoKind>(), validation_err);
565+ assert_eq!(" seconds 22 ".parse::<UndoKind>(), validation_err);
566+ assert_eq!(" -4 m".parse::<UndoKind>(), validation_err);
567+ assert_eq!("5s 3".parse::<UndoKind>(), validation_err);
568+
569+ // Units are u64.
570+ assert_eq!(
571+ "18446744073709551616minutes".parse::<UndoKind>(),
572+ Err("integer too large: 18446744073709551616".to_string())
573+ );
574+
575+ // Units are validated.
576+ assert_eq!(
577+ "1 millennium".parse::<UndoKind>(),
578+ Err("incorrect time unit: millennium".to_string())
579+ );
580+
581+ // Units can't be specified twice.
582+ assert_eq!(
583+ "2 seconds 6s".parse::<UndoKind>(),
584+ Err("seconds specified more than once".to_string())
585+ );
586+
587+ // Various formats are correctly handled.
588+ assert_eq!(
589+ "4s".parse::<UndoKind>(),
590+ Ok(TimePeriod(Duration::from_secs(4)))
591+ );
592+ assert_eq!(
593+ "2m".parse::<UndoKind>(),
594+ Ok(TimePeriod(Duration::from_secs(120)))
595+ );
596+ assert_eq!(
597+ "5h".parse::<UndoKind>(),
598+ Ok(TimePeriod(Duration::from_secs(5 * 60 * 60)))
599+ );
600+ assert_eq!(
601+ "3d".parse::<UndoKind>(),
602+ Ok(TimePeriod(Duration::from_secs(3 * 24 * 60 * 60)))
603+ );
604+ assert_eq!(
605+ "1m30s".parse::<UndoKind>(),
606+ Ok(TimePeriod(Duration::from_secs(90)))
607+ );
608+ assert_eq!(
609+ "1m 20 seconds".parse::<UndoKind>(),
610+ Ok(TimePeriod(Duration::from_secs(80)))
611+ );
612+ assert_eq!(
613+ " 2 minute 1day".parse::<UndoKind>(),
614+ Ok(TimePeriod(Duration::from_secs(24 * 60 * 60 + 2 * 60)))
615+ );
616+ assert_eq!(
617+ "3 d 2hour 5 minutes 30sec".parse::<UndoKind>(),
618+ Ok(TimePeriod(Duration::from_secs(
619+ 3 * 24 * 60 * 60 + 2 * 60 * 60 + 5 * 60 + 30
620+ )))
621+ );
622+
623+ // Sum overflow is handled.
624+ assert_eq!(
625+ "18446744073709551615minutes".parse::<UndoKind>(),
626+ Err("duration too large".to_string())
627+ );
628+ assert_eq!(
629+ "1 minute 18446744073709551615 seconds".parse::<UndoKind>(),
630+ Err("duration too large".to_string())
631+ );
139 }
632 }
140}
633}
helix-core/src/increment/date_time.rsnew file+320
@@ -0,0 +1,320 @@
1+use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime};
2+use once_cell::sync::Lazy;
3+use regex::Regex;
4+use std::fmt::Write;
5+
6+/// Increment a Date or DateTime
7+///
8+/// If just a Date is selected the day will be incremented.
9+/// If a DateTime is selected the second will be incremented.
10+pub fn increment(selected_text: &str, amount: i64) -> Option<String> {
11+ if selected_text.is_empty() {
12+ return None;
13+ }
14+
15+ FORMATS.iter().find_map(|format| {
16+ let captures = format.regex.captures(selected_text)?;
17+ if captures.len() - 1 != format.fields.len() {
18+ return None;
19+ }
20+
21+ let date_time = captures.get(0)?;
22+ let has_date = format.fields.iter().any(|f| f.unit.is_date());
23+ let has_time = format.fields.iter().any(|f| f.unit.is_time());
24+ let date_time = &selected_text[date_time.start()..date_time.end()];
25+ match (has_date, has_time) {
26+ (true, true) => {
27+ let date_time = NaiveDateTime::parse_from_str(date_time, format.fmt).ok()?;
28+ Some(
29+ date_time
30+ .checked_add_signed(Duration::minutes(amount))?
31+ .format(format.fmt)
32+ .to_string(),
33+ )
34+ }
35+ (true, false) => {
36+ let date = NaiveDate::parse_from_str(date_time, format.fmt).ok()?;
37+ Some(
38+ date.checked_add_signed(Duration::days(amount))?
39+ .format(format.fmt)
40+ .to_string(),
41+ )
42+ }
43+ (false, true) => {
44+ let time = NaiveTime::parse_from_str(date_time, format.fmt).ok()?;
45+ let (adjusted_time, _) = time.overflowing_add_signed(Duration::minutes(amount));
46+ Some(adjusted_time.format(format.fmt).to_string())
47+ }
48+ (false, false) => None,
49+ }
50+ })
51+}
52+
53+static FORMATS: Lazy<Vec<Format>> = Lazy::new(|| {
54+ vec![
55+ Format::new("%Y-%m-%d %H:%M:%S"), // 2021-11-24 07:12:23
56+ Format::new("%Y/%m/%d %H:%M:%S"), // 2021/11/24 07:12:23
57+ Format::new("%Y-%m-%d %H:%M"), // 2021-11-24 07:12
58+ Format::new("%Y/%m/%d %H:%M"), // 2021/11/24 07:12
59+ Format::new("%Y-%m-%d"), // 2021-11-24
60+ Format::new("%Y/%m/%d"), // 2021/11/24
61+ Format::new("%a %b %d %Y"), // Wed Nov 24 2021
62+ Format::new("%d-%b-%Y"), // 24-Nov-2021
63+ Format::new("%Y %b %d"), // 2021 Nov 24
64+ Format::new("%b %d, %Y"), // Nov 24, 2021
65+ Format::new("%-I:%M:%S %P"), // 7:21:53 am
66+ Format::new("%-I:%M %P"), // 7:21 am
67+ Format::new("%-I:%M:%S %p"), // 7:21:53 AM
68+ Format::new("%-I:%M %p"), // 7:21 AM
69+ Format::new("%H:%M:%S"), // 23:24:23
70+ Format::new("%H:%M"), // 23:24
71+ ]
72+});
73+
74+#[derive(Debug)]
75+struct Format {
76+ fmt: &'static str,
77+ fields: Vec<DateField>,
78+ regex: Regex,
79+ max_len: usize,
80+}
81+
82+impl Format {
83+ fn new(fmt: &'static str) -> Self {
84+ let mut remaining = fmt;
85+ let mut fields = Vec::new();
86+ let mut regex = "^".to_string();
87+ let mut max_len = 0;
88+
89+ while let Some(i) = remaining.find('%') {
90+ let after = &remaining[i + 1..];
91+ let mut chars = after.chars();
92+ let c = chars.next().unwrap();
93+
94+ let spec_len = if c == '-' {
95+ 1 + chars.next().unwrap().len_utf8()
96+ } else {
97+ c.len_utf8()
98+ };
99+
100+ let specifier = &after[..spec_len];
101+ let field = DateField::from_specifier(specifier).unwrap();
102+ fields.push(field);
103+ max_len += field.max_len + remaining[..i].len();
104+ regex += &remaining[..i];
105+ write!(regex, "({})", field.regex).unwrap();
106+ remaining = &after[spec_len..];
107+ }
108+ regex += "$";
109+
110+ let regex = Regex::new(&regex).unwrap();
111+
112+ Self {
113+ fmt,
114+ fields,
115+ regex,
116+ max_len,
117+ }
118+ }
119+}
120+
121+impl PartialEq for Format {
122+ fn eq(&self, other: &Self) -> bool {
123+ self.fmt == other.fmt && self.fields == other.fields && self.max_len == other.max_len
124+ }
125+}
126+
127+impl Eq for Format {}
128+
129+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
130+struct DateField {
131+ regex: &'static str,
132+ unit: DateUnit,
133+ max_len: usize,
134+}
135+
136+impl DateField {
137+ fn from_specifier(specifier: &str) -> Option<Self> {
138+ match specifier {
139+ "Y" => Some(Self {
140+ regex: r"\d{4}",
141+ unit: DateUnit::Years,
142+ max_len: 5,
143+ }),
144+ "y" => Some(Self {
145+ regex: r"\d\d",
146+ unit: DateUnit::Years,
147+ max_len: 2,
148+ }),
149+ "m" => Some(Self {
150+ regex: r"[0-1]\d",
151+ unit: DateUnit::Months,
152+ max_len: 2,
153+ }),
154+ "d" => Some(Self {
155+ regex: r"[0-3]\d",
156+ unit: DateUnit::Days,
157+ max_len: 2,
158+ }),
159+ "-d" => Some(Self {
160+ regex: r"[1-3]?\d",
161+ unit: DateUnit::Days,
162+ max_len: 2,
163+ }),
164+ "a" => Some(Self {
165+ regex: r"Sun|Mon|Tue|Wed|Thu|Fri|Sat",
166+ unit: DateUnit::Days,
167+ max_len: 3,
168+ }),
169+ "A" => Some(Self {
170+ regex: r"Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday",
171+ unit: DateUnit::Days,
172+ max_len: 9,
173+ }),
174+ "b" | "h" => Some(Self {
175+ regex: r"Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec",
176+ unit: DateUnit::Months,
177+ max_len: 3,
178+ }),
179+ "B" => Some(Self {
180+ regex: r"January|February|March|April|May|June|July|August|September|October|November|December",
181+ unit: DateUnit::Months,
182+ max_len: 9,
183+ }),
184+ "H" => Some(Self {
185+ regex: r"[0-2]\d",
186+ unit: DateUnit::Hours,
187+ max_len: 2,
188+ }),
189+ "M" => Some(Self {
190+ regex: r"[0-5]\d",
191+ unit: DateUnit::Minutes,
192+ max_len: 2,
193+ }),
194+ "S" => Some(Self {
195+ regex: r"[0-5]\d",
196+ unit: DateUnit::Seconds,
197+ max_len: 2,
198+ }),
199+ "I" => Some(Self {
200+ regex: r"[0-1]\d",
201+ unit: DateUnit::Hours,
202+ max_len: 2,
203+ }),
204+ "-I" => Some(Self {
205+ regex: r"1?\d",
206+ unit: DateUnit::Hours,
207+ max_len: 2,
208+ }),
209+ "P" => Some(Self {
210+ regex: r"am|pm",
211+ unit: DateUnit::AmPm,
212+ max_len: 2,
213+ }),
214+ "p" => Some(Self {
215+ regex: r"AM|PM",
216+ unit: DateUnit::AmPm,
217+ max_len: 2,
218+ }),
219+ _ => None,
220+ }
221+ }
222+}
223+
224+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
225+enum DateUnit {
226+ Years,
227+ Months,
228+ Days,
229+ Hours,
230+ Minutes,
231+ Seconds,
232+ AmPm,
233+}
234+
235+impl DateUnit {
236+ fn is_date(self) -> bool {
237+ matches!(self, DateUnit::Years | DateUnit::Months | DateUnit::Days)
238+ }
239+
240+ fn is_time(self) -> bool {
241+ matches!(
242+ self,
243+ DateUnit::Hours | DateUnit::Minutes | DateUnit::Seconds
244+ )
245+ }
246+}
247+
248+#[cfg(test)]
249+mod test {
250+ use super::*;
251+
252+ #[test]
253+ fn test_increment_date_times() {
254+ let tests = [
255+ // (original, cursor, amount, expected)
256+ ("2020-02-28", 1, "2020-02-29"),
257+ ("2020-02-29", 1, "2020-03-01"),
258+ ("2020-01-31", 1, "2020-02-01"),
259+ ("2020-01-20", 1, "2020-01-21"),
260+ ("2021-01-01", -1, "2020-12-31"),
261+ ("2021-01-31", -2, "2021-01-29"),
262+ ("2020-02-28", 1, "2020-02-29"),
263+ ("2021-02-28", 1, "2021-03-01"),
264+ ("2021-03-01", -1, "2021-02-28"),
265+ ("2020-02-29", -1, "2020-02-28"),
266+ ("2020-02-20", -1, "2020-02-19"),
267+ ("2021-03-01", -1, "2021-02-28"),
268+ ("1980/12/21", 100, "1981/03/31"),
269+ ("1980/12/21", -100, "1980/09/12"),
270+ ("1980/12/21", 1000, "1983/09/17"),
271+ ("1980/12/21", -1000, "1978/03/27"),
272+ ("2021-11-24 07:12:23", 1, "2021-11-24 07:13:23"),
273+ ("2021-11-24 07:12", 1, "2021-11-24 07:13"),
274+ ("Wed Nov 24 2021", 1, "Thu Nov 25 2021"),
275+ ("24-Nov-2021", 1, "25-Nov-2021"),
276+ ("2021 Nov 24", 1, "2021 Nov 25"),
277+ ("Nov 24, 2021", 1, "Nov 25, 2021"),
278+ ("7:21:53 am", 1, "7:22:53 am"),
279+ ("7:21:53 AM", 1, "7:22:53 AM"),
280+ ("7:21 am", 1, "7:22 am"),
281+ ("23:24:23", 1, "23:25:23"),
282+ ("23:24", 1, "23:25"),
283+ ("23:59", 1, "00:00"),
284+ ("23:59:59", 1, "00:00:59"),
285+ ];
286+
287+ for (original, amount, expected) in tests {
288+ assert_eq!(increment(original, amount).unwrap(), expected);
289+ }
290+ }
291+
292+ #[test]
293+ fn test_invalid_date_times() {
294+ let tests = [
295+ "0000-00-00",
296+ "1980-2-21",
297+ "1980-12-1",
298+ "12345",
299+ "2020-02-30",
300+ "1999-12-32",
301+ "19-12-32",
302+ "1-2-3",
303+ "0000/00/00",
304+ "1980/2/21",
305+ "1980/12/1",
306+ "12345",
307+ "2020/02/30",
308+ "1999/12/32",
309+ "19/12/32",
310+ "1/2/3",
311+ "123:456:789",
312+ "11:61",
313+ "2021-55-12 08:12:54",
314+ ];
315+
316+ for invalid in tests {
317+ assert_eq!(increment(invalid, 1), None)
318+ }
319+ }
320+}
helix-core/src/increment/integer.rsnew file+235
@@ -0,0 +1,235 @@
1+const SEPARATOR: char = '_';
2+
3+/// Increment an integer.
4+///
5+/// Supported bases:
6+/// 2 with prefix 0b
7+/// 8 with prefix 0o
8+/// 10 with no prefix
9+/// 16 with prefix 0x
10+///
11+/// An integer can contain `_` as a separator but may not start or end with a separator.
12+/// Base 10 integers can go negative, but bases 2, 8, and 16 cannot.
13+/// All addition and subtraction is saturating.
14+pub fn increment(selected_text: &str, amount: i64) -> Option<String> {
15+ if selected_text.is_empty()
16+ || selected_text.ends_with(SEPARATOR)
17+ || selected_text.starts_with(SEPARATOR)
18+ {
19+ return None;
20+ }
21+
22+ let radix = if selected_text.starts_with("0x") {
23+ 16
24+ } else if selected_text.starts_with("0o") {
25+ 8
26+ } else if selected_text.starts_with("0b") {
27+ 2
28+ } else {
29+ 10
30+ };
31+
32+ // Get separator indexes from right to left.
33+ let separator_rtl_indexes: Vec<usize> = selected_text
34+ .chars()
35+ .rev()
36+ .enumerate()
37+ .filter_map(|(i, c)| if c == SEPARATOR { Some(i) } else { None })
38+ .collect();
39+
40+ let word: String = selected_text.chars().filter(|&c| c != SEPARATOR).collect();
41+
42+ let mut new_text = if radix == 10 {
43+ let number = &word;
44+ let value = i128::from_str_radix(number, radix).ok()?;
45+ let new_value = value.saturating_add(amount as i128);
46+
47+ let format_length = match (value.is_negative(), new_value.is_negative()) {
48+ (true, false) => number.len() - 1,
49+ (false, true) => number.len() + 1,
50+ _ => number.len(),
51+ } - separator_rtl_indexes.len();
52+
53+ if number.starts_with('0') || number.starts_with("-0") {
54+ format!("{:01$}", new_value, format_length)
55+ } else {
56+ format!("{}", new_value)
57+ }
58+ } else {
59+ let number = &word[2..];
60+ let value = u128::from_str_radix(number, radix).ok()?;
61+ let new_value = (value as i128).saturating_add(amount as i128);
62+ let new_value = if new_value < 0 { 0 } else { new_value };
63+ let format_length = selected_text.len() - 2 - separator_rtl_indexes.len();
64+
65+ match radix {
66+ 2 => format!("0b{:01$b}", new_value, format_length),
67+ 8 => format!("0o{:01$o}", new_value, format_length),
68+ 16 => {
69+ let (lower_count, upper_count): (usize, usize) =
70+ number.chars().fold((0, 0), |(lower, upper), c| {
71+ (
72+ lower + c.is_ascii_lowercase() as usize,
73+ upper + c.is_ascii_uppercase() as usize,
74+ )
75+ });
76+ if upper_count > lower_count {
77+ format!("0x{:01$X}", new_value, format_length)
78+ } else {
79+ format!("0x{:01$x}", new_value, format_length)
80+ }
81+ }
82+ _ => unimplemented!("radix not supported: {}", radix),
83+ }
84+ };
85+
86+ // Add separators from original number.
87+ for &rtl_index in &separator_rtl_indexes {
88+ if rtl_index < new_text.len() {
89+ let new_index = new_text.len().saturating_sub(rtl_index);
90+ if new_index > 0 {
91+ new_text.insert(new_index, SEPARATOR);
92+ }
93+ }
94+ }
95+
96+ // Add in additional separators if necessary.
97+ if new_text.len() > selected_text.len() && !separator_rtl_indexes.is_empty() {
98+ let spacing = match separator_rtl_indexes.as_slice() {
99+ [.., b, a] => a - b - 1,
100+ _ => separator_rtl_indexes[0],
101+ };
102+
103+ let prefix_length = if radix == 10 { 0 } else { 2 };
104+ if let Some(mut index) = new_text.find(SEPARATOR) {
105+ while index - prefix_length > spacing {
106+ index -= spacing;
107+ new_text.insert(index, SEPARATOR);
108+ }
109+ }
110+ }
111+
112+ Some(new_text)
113+}
114+
115+#[cfg(test)]
116+mod test {
117+ use super::*;
118+
119+ #[test]
120+ fn test_increment_basic_decimal_numbers() {
121+ let tests = [
122+ ("100", 1, "101"),
123+ ("100", -1, "99"),
124+ ("99", 1, "100"),
125+ ("100", 1000, "1100"),
126+ ("100", -1000, "-900"),
127+ ("-1", 1, "0"),
128+ ("-1", 2, "1"),
129+ ("1", -1, "0"),
130+ ("1", -2, "-1"),
131+ ];
132+
133+ for (original, amount, expected) in tests {
134+ assert_eq!(increment(original, amount).unwrap(), expected);
135+ }
136+ }
137+
138+ #[test]
139+ fn test_increment_basic_hexadecimal_numbers() {
140+ let tests = [
141+ ("0x0100", 1, "0x0101"),
142+ ("0x0100", -1, "0x00ff"),
143+ ("0x0001", -1, "0x0000"),
144+ ("0x0000", -1, "0x0000"),
145+ ("0xffffffffffffffff", 1, "0x10000000000000000"),
146+ ("0xffffffffffffffff", 2, "0x10000000000000001"),
147+ ("0xffffffffffffffff", -1, "0xfffffffffffffffe"),
148+ ("0xABCDEF1234567890", 1, "0xABCDEF1234567891"),
149+ ("0xabcdef1234567890", 1, "0xabcdef1234567891"),
150+ ];
151+
152+ for (original, amount, expected) in tests {
153+ assert_eq!(increment(original, amount).unwrap(), expected);
154+ }
155+ }
156+
157+ #[test]
158+ fn test_increment_basic_octal_numbers() {
159+ let tests = [
160+ ("0o0107", 1, "0o0110"),
161+ ("0o0110", -1, "0o0107"),
162+ ("0o0001", -1, "0o0000"),
163+ ("0o7777", 1, "0o10000"),
164+ ("0o1000", -1, "0o0777"),
165+ ("0o0107", 10, "0o0121"),
166+ ("0o0000", -1, "0o0000"),
167+ ("0o1777777777777777777777", 1, "0o2000000000000000000000"),
168+ ("0o1777777777777777777777", 2, "0o2000000000000000000001"),
169+ ("0o1777777777777777777777", -1, "0o1777777777777777777776"),
170+ ];
171+
172+ for (original, amount, expected) in tests {
173+ assert_eq!(increment(original, amount).unwrap(), expected);
174+ }
175+ }
176+
177+ #[test]
178+ fn test_increment_basic_binary_numbers() {
179+ let tests = [
180+ ("0b00000100", 1, "0b00000101"),
181+ ("0b00000100", -1, "0b00000011"),
182+ ("0b00000100", 2, "0b00000110"),
183+ ("0b00000100", -2, "0b00000010"),
184+ ("0b00000001", -1, "0b00000000"),
185+ ("0b00111111", 10, "0b01001001"),
186+ ("0b11111111", 1, "0b100000000"),
187+ ("0b10000000", -1, "0b01111111"),
188+ ("0b0000", -1, "0b0000"),
189+ (
190+ "0b1111111111111111111111111111111111111111111111111111111111111111",
191+ 1,
192+ "0b10000000000000000000000000000000000000000000000000000000000000000",
193+ ),
194+ (
195+ "0b1111111111111111111111111111111111111111111111111111111111111111",
196+ 2,
197+ "0b10000000000000000000000000000000000000000000000000000000000000001",
198+ ),
199+ (
200+ "0b1111111111111111111111111111111111111111111111111111111111111111",
201+ -1,
202+ "0b1111111111111111111111111111111111111111111111111111111111111110",
203+ ),
204+ ];
205+
206+ for (original, amount, expected) in tests {
207+ assert_eq!(increment(original, amount).unwrap(), expected);
208+ }
209+ }
210+
211+ #[test]
212+ fn test_increment_with_separators() {
213+ let tests = [
214+ ("999_999", 1, "1_000_000"),
215+ ("1_000_000", -1, "999_999"),
216+ ("-999_999", -1, "-1_000_000"),
217+ ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"),
218+ ("0x0000_0000", -1, "0x0000_0000"),
219+ ("0x0000_0000_0000", -1, "0x0000_0000_0000"),
220+ ("0b01111111_11111111", 1, "0b10000000_00000000"),
221+ ("0b11111111_11111111", 1, "0b1_00000000_00000000"),
222+ ];
223+
224+ for (original, amount, expected) in tests {
225+ assert_eq!(increment(original, amount).unwrap(), expected);
226+ }
227+ }
228+
229+ #[test]
230+ fn test_leading_and_trailing_separators_arent_a_match() {
231+ assert_eq!(increment("9_", 1), None);
232+ assert_eq!(increment("_9", 1), None);
233+ assert_eq!(increment("_9_", 1), None);
234+ }
235+}
helix-core/src/increment/mod.rsnew file+10
@@ -0,0 +1,10 @@
1+mod date_time;
2+mod integer;
3+
4+pub fn integer(selected_text: &str, amount: i64) -> Option<String> {
5+ integer::increment(selected_text, amount)
6+}
7+
8+pub fn date_time(selected_text: &str, amount: i64) -> Option<String> {
9+ date_time::increment(selected_text, amount)
10+}
helix-core/src/indent.rs+1201−109
@@ -1,180 +1,1272 @@
1+use std::{borrow::Cow, collections::HashMap};
2+
3+use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
4+
1use crate::{
5use crate::{
2− syntax::Syntax,
6+ chars::{char_is_line_ending, char_is_whitespace},
7+ find_first_non_whitespace_char,
8+ graphemes::{grapheme_width, tab_width_at},
9+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
3− tree_sitter::{Node, Tree},
10+ tree_sitter::Node,
4− Rope, RopeSlice, State,
11+ Position, Rope, RopeGraphemes, RopeSlice,
5};
12};
6
13
7−/// To determine indentation of a newly inserted line, figure out the indentation at the last col
14+/// Enum representing indentation style.
8−/// of the previous line.
15+///
16+/// Only values 1-8 are valid for the `Spaces` variant.
17+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
18+pub enum IndentStyle {
19+ Tabs,
20+ Spaces(u8),
21+}
22+
23+// 16 spaces
24+const INDENTS: &str = " ";
25+pub const MAX_INDENT: u8 = 16;
26+
27+impl IndentStyle {
28+ /// Creates an `IndentStyle` from an indentation string.
29+ ///
30+ /// For example, passing `" "` (four spaces) will create `IndentStyle::Spaces(4)`.
31+ #[allow(clippy::should_implement_trait)]
32+ #[inline]
33+ pub fn from_str(indent: &str) -> Self {
34+ // XXX: do we care about validating the input more than this? Probably not...?
35+ debug_assert!(!indent.is_empty() && indent.len() <= MAX_INDENT as usize);
36+
37+ if indent.starts_with(' ') {
38+ IndentStyle::Spaces(indent.len().clamp(1, MAX_INDENT as usize) as u8)
39+ } else {
40+ IndentStyle::Tabs
41+ }
42+ }
43+
44+ #[inline]
45+ pub fn as_str(&self) -> &'static str {
46+ match *self {
47+ IndentStyle::Tabs => "\t",
48+ IndentStyle::Spaces(n) => {
49+ // Unsupported indentation style. This should never happen,
50+ debug_assert!(n > 0 && n <= MAX_INDENT);
51+
52+ // Either way, clamp to the nearest supported value
53+ let closest_n = n.clamp(1, MAX_INDENT) as usize;
54+ &INDENTS[0..closest_n]
55+ }
56+ }
57+ }
58+
59+ #[inline]
60+ pub fn indent_width(&self, tab_width: usize) -> usize {
61+ match *self {
62+ IndentStyle::Tabs => tab_width,
63+ IndentStyle::Spaces(width) => width as usize,
64+ }
65+ }
66+}
67+
68+/// Attempts to detect the indentation style used in a document.
69+///
70+/// Returns the indentation style if the auto-detect confidence is
71+/// reasonably high, otherwise returns `None`.
72+pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
73+ // Build a histogram of the indentation *increases* between
74+ // subsequent lines, ignoring lines that are all whitespace.
75+ //
76+ // Index 0 is for tabs, the rest are 1-MAX_INDENT spaces.
77+ let histogram: [usize; MAX_INDENT as usize + 1] = {
78+ let mut histogram = [0; MAX_INDENT as usize + 1];
79+ let mut prev_line_is_tabs = false;
80+ let mut prev_line_leading_count = 0usize;
9
81
10−pub const TAB_WIDTH: usize = 4;
82+ // Loop through the lines, checking for and recording indentation
83+ // increases as we go.
84+ 'outer: for line in document_text.lines().take(1000) {
85+ let mut c_iter = line.chars();
86+
87+ // Is first character a tab or space?
88+ let is_tabs = match c_iter.next() {
89+ Some('\t') => true,
90+ Some(' ') => false,
91+
92+ // Ignore blank lines.
93+ Some(c) if char_is_line_ending(c) => continue,
94+
95+ _ => {
96+ prev_line_is_tabs = false;
97+ prev_line_leading_count = 0;
98+ continue;
99+ }
100+ };
101+
102+ // Count the line's total leading tab/space characters.
103+ let mut leading_count = 1;
104+ let mut count_is_done = false;
105+ for c in c_iter {
106+ match c {
107+ '\t' if is_tabs && !count_is_done => leading_count += 1,
108+ ' ' if !is_tabs && !count_is_done => leading_count += 1,
109+
110+ // We stop counting if we hit whitespace that doesn't
111+ // qualify as indent or doesn't match the leading
112+ // whitespace, but we don't exit the loop yet because
113+ // we still want to determine if the line is blank.
114+ c if char_is_whitespace(c) => count_is_done = true,
115+
116+ // Ignore blank lines.
117+ c if char_is_line_ending(c) => continue 'outer,
118+
119+ _ => break,
120+ }
121+
122+ // Bound the worst-case execution time for weird text files.
123+ if leading_count > 256 {
124+ continue 'outer;
125+ }
126+ }
127+
128+ // If there was an increase in indentation over the previous
129+ // line, update the histogram with that increase.
130+ if (prev_line_is_tabs == is_tabs || prev_line_leading_count == 0)
131+ && prev_line_leading_count < leading_count
132+ {
133+ if is_tabs {
134+ histogram[0] += 1;
135+ } else {
136+ let amount = leading_count - prev_line_leading_count;
137+ if amount <= MAX_INDENT as usize {
138+ histogram[amount] += 1;
139+ }
140+ }
141+ }
142+
143+ // Store this line's leading whitespace info for use with
144+ // the next line.
145+ prev_line_is_tabs = is_tabs;
146+ prev_line_leading_count = leading_count;
147+ }
148+
149+ // Give more weight to tabs, because their presence is a very
150+ // strong indicator.
151+ histogram[0] *= 2;
152+
153+ histogram
154+ };
155+
156+ // Find the most frequent indent, its frequency, and the frequency of
157+ // the next-most frequent indent.
158+ let indent = histogram
159+ .iter()
160+ .enumerate()
161+ .max_by_key(|kv| kv.1)
162+ .unwrap()
163+ .0;
164+ let indent_freq = histogram[indent];
165+ let indent_freq_2 = *histogram
166+ .iter()
167+ .enumerate()
168+ .filter(|kv| kv.0 != indent)
169+ .map(|kv| kv.1)
170+ .max()
171+ .unwrap();
172+
173+ // Return the the auto-detected result if we're confident enough in its
174+ // accuracy, based on some heuristics.
175+ if indent_freq >= 1 && (indent_freq_2 as f64 / indent_freq as f64) < 0.66 {
176+ Some(match indent {
177+ 0 => IndentStyle::Tabs,
178+ _ => IndentStyle::Spaces(indent as u8),
179+ })
180+ } else {
181+ None
182+ }
183+}
11
184
185+/// To determine indentation of a newly inserted line, figure out the indentation at the last col
186+/// of the previous line.
12−fn indent_level_for_line(line: RopeSlice) -> usize {
187+pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: usize) -> usize {
13 let mut len = 0;
188 let mut len = 0;
14 for ch in line.chars() {
189 for ch in line.chars() {
15 match ch {
190 match ch {
16− '\t' => len += TAB_WIDTH,
191+ '\t' => len += tab_width_at(len, tab_width as u16),
17 ' ' => len += 1,
192 ' ' => len += 1,
18 _ => break,
193 _ => break,
19 }
194 }
20 }
195 }
21
196
22− len / TAB_WIDTH
197+ len / indent_width
23}
198}
24
199
25−/// Find the highest syntax node at position.
200+/// Create a string of tabs & spaces that has the same visual width as the given RopeSlice (independent of the tab width).
26−/// This is to identify the column where this node (e.g., an HTML closing tag) ends.
201+fn whitespace_with_same_width(text: RopeSlice) -> String {
27−fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Node> {
202+ let mut s = String::new();
28− let tree = syntax.root_layer.tree.as_ref().unwrap();
203+ for grapheme in RopeGraphemes::new(text) {
204+ if grapheme == "\t" {
205+ s.push('\t');
206+ } else {
207+ s.extend(std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))));
208+ }
209+ }
210+ s
211+}
29
212
30− let mut node = match tree.root_node().named_descendant_for_byte_range(pos, pos) {
213+fn add_indent_level(
31− Some(node) => node,
214+ mut base_indent: String,
32− None => return None,
215+ added_indent_level: isize,
216+ indent_style: &IndentStyle,
217+ tab_width: usize,
218+) -> String {
219+ if added_indent_level >= 0 {
220+ // Adding a non-negative indent is easy, we can simply append the indent string
221+ base_indent.push_str(&indent_style.as_str().repeat(added_indent_level as usize));
222+ base_indent
223+ } else {
224+ // In this case, we want to return a prefix of `base_indent`.
225+ // Since the width of a tab depends on its offset, we cannot simply iterate over
226+ // the chars of `base_indent` in reverse until we have the desired indent reduction,
227+ // instead we iterate over them twice in forward direction.
228+ let base_indent_rope = RopeSlice::from(base_indent.as_str());
229+ #[allow(deprecated)]
230+ let base_indent_width =
231+ crate::visual_coords_at_pos(base_indent_rope, base_indent_rope.len_chars(), tab_width)
232+ .col;
233+ let target_indent_width = base_indent_width
234+ .saturating_sub((-added_indent_level) as usize * indent_style.indent_width(tab_width));
235+ #[allow(deprecated)]
236+ let char_end_idx = crate::pos_at_visual_coords(
237+ base_indent_rope,
238+ Position {
239+ row: 0,
240+ col: target_indent_width,
33− };
241+ },
242+ tab_width,
243+ );
244+ let byte_end_idx = base_indent_rope.char_to_byte(char_end_idx);
245+ base_indent.truncate(byte_end_idx);
246+ base_indent
247+ }
248+}
34
249
35− while let Some(parent) = node.parent() {
250+/// Computes for node and all ancestors whether they are the first node on their line.
36− if parent.start_byte() == node.start_byte() {
251+/// The first entry in the return value represents the root node, the last one the node itself
252+fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
253+ let mut first_in_line = Vec::new();
254+ loop {
255+ if let Some(prev) = node.prev_sibling() {
256+ // If we insert a new line, the first node at/after the cursor is considered to be the first in its line
257+ let first = prev.end_position().row != node.start_position().row
258+ || new_line_byte_pos.map_or(false, |byte_pos| {
259+ node.start_byte() >= byte_pos && prev.start_byte() < byte_pos
260+ });
261+ first_in_line.push(Some(first));
262+ } else {
263+ // Nodes that have no previous siblings are first in their line if and only if their parent is
264+ // (which we don't know yet)
265+ first_in_line.push(None);
266+ }
267+ if let Some(parent) = node.parent() {
37− node = parent
268+ node = parent;
38 } else {
269 } else {
39 break;
270 break;
40 }
271 }
41 }
272 }
42
273
43− Some(node)
274+ let mut result = Vec::with_capacity(first_in_line.len());
275+ let mut parent_is_first = true; // The root node is by definition the first node in its line
276+ for first in first_in_line.into_iter().rev() {
277+ if let Some(first) = first {
278+ result.push(first);
279+ parent_is_first = first;
280+ } else {
281+ result.push(parent_is_first);
282+ }
283+ }
284+ result
44}
285}
45
286
46−fn walk(node: Option<Node>) -> usize {
287+/// The total indent for some line of code.
47− let node = match node {
288+/// This is usually constructed in one of 2 ways:
48− Some(node) => node,
289+/// - Successively add indent captures to get the (added) indent from a single line
49− None => return 0,
290+/// - Successively add the indent results for each line
291+/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
292+/// - max(0, indent - outdent) tabs, if tabs are used for indentation
293+/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
294+#[derive(Default, Debug, PartialEq, Eq, Clone)]
295+pub struct Indentation<'a> {
296+ indent: usize,
297+ indent_always: usize,
298+ outdent: usize,
299+ outdent_always: usize,
300+ /// The alignment, as a string containing only tabs & spaces. Storing this as a string instead of e.g.
301+ /// the (visual) width ensures that the alignment is preserved even if the tab width changes.
302+ align: Option<RopeSlice<'a>>,
50− };
303+}
51
304
52− let parent = match node.parent() {
305+impl<'a> Indentation<'a> {
53− Some(node) => node,
306+ /// Add some other [Indentation] to this.
54− None => return 0,
307+ /// The added indent should be the total added indent from one line.
308+ /// Indent should always be added starting from the bottom (or equivalently, the innermost tree-sitter node).
309+ fn add_line(&mut self, added: Indentation<'a>) {
310+ // Align overrides the indent from outer scopes.
311+ if self.align.is_some() {
312+ return;
313+ }
314+ if added.align.is_some() {
315+ self.align = added.align;
316+ return;
317+ }
318+ self.indent += added.indent;
319+ self.indent_always += added.indent_always;
320+ self.outdent += added.outdent;
321+ self.outdent_always += added.outdent_always;
55− };
322+ }
56
323
57− let mut increment = 0;
324+ /// Add an indent capture to this indent.
325+ /// Only captures that apply to the same line should be added together in this way (otherwise use `add_line`)
326+ /// and the captures should be added starting from the innermost tree-sitter node (currently this only matters
327+ /// if multiple `@align` patterns occur on the same line).
328+ fn add_capture(&mut self, added: IndentCaptureType<'a>) {
329+ match added {
330+ IndentCaptureType::Indent => {
331+ if self.indent_always == 0 {
332+ self.indent = 1;
333+ }
334+ }
335+ IndentCaptureType::IndentAlways => {
336+ // any time we encounter an `indent.always` on the same line, we
337+ // want to cancel out all regular indents
338+ self.indent_always += 1;
339+ self.indent = 0;
340+ }
341+ IndentCaptureType::Outdent => {
342+ if self.outdent_always == 0 {
343+ self.outdent = 1;
344+ }
345+ }
346+ IndentCaptureType::OutdentAlways => {
347+ self.outdent_always += 1;
348+ self.outdent = 0;
349+ }
350+ IndentCaptureType::Align(align) => {
351+ if self.align.is_none() {
352+ self.align = Some(align);
353+ }
354+ }
355+ }
356+ }
357+ fn net_indent(&self) -> isize {
358+ (self.indent + self.indent_always) as isize
359+ - ((self.outdent + self.outdent_always) as isize)
360+ }
361+ /// Convert `self` into a string, taking into account the computed and actual indentation of some other line.
362+ fn relative_indent(
363+ &self,
364+ other_computed_indent: &Self,
365+ other_leading_whitespace: RopeSlice,
366+ indent_style: &IndentStyle,
367+ tab_width: usize,
368+ ) -> Option<String> {
369+ if self.align == other_computed_indent.align {
370+ // If self and baseline are either not aligned to anything or both aligned the same way,
371+ // we can simply take `other_leading_whitespace` and add some indent / outdent to it (in the second
372+ // case, the alignment should already be accounted for in `other_leading_whitespace`).
373+ let indent_diff = self.net_indent() - other_computed_indent.net_indent();
374+ Some(add_indent_level(
375+ String::from(other_leading_whitespace),
376+ indent_diff,
377+ indent_style,
378+ tab_width,
379+ ))
380+ } else {
381+ // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
382+ None
383+ }
384+ }
385+ pub fn to_string(&self, indent_style: &IndentStyle, tab_width: usize) -> String {
386+ add_indent_level(
387+ self.align
388+ .map_or_else(String::new, whitespace_with_same_width),
389+ self.net_indent(),
390+ indent_style,
391+ tab_width,
392+ )
393+ }
394+}
58
395
59− // Hardcoded for rust for now
396+/// An indent definition which corresponds to a capture from the indent query
60− let indent_scopes = &[
397+#[derive(Debug)]
61− "block",
398+struct IndentCapture<'a> {
62− "function_item",
399+ capture_type: IndentCaptureType<'a>,
63− "closure_expression",
400+ scope: IndentScope,
64− "while_expression",
401+}
65− "for_expression",
402+#[derive(Debug, Clone, PartialEq)]
66− "loop_expression",
403+enum IndentCaptureType<'a> {
67− "if_expression",
404+ Indent,
68− "if_let_expression",
405+ IndentAlways,
69− "binary_expression",
406+ Outdent,
70− "match_expression",
407+ OutdentAlways,
71− "match_arm",
408+ /// Alignment given as a string of whitespace
72− //
409+ Align(RopeSlice<'a>),
73− "struct_item",
410+}
74− "enum_item",
75− "impl_item",
76− //
77− "mod_item",
78− ];
79
411
80− // let indent_except_first_scopes = &[];
412+impl<'a> IndentCaptureType<'a> {
413+ fn default_scope(&self) -> IndentScope {
414+ match self {
415+ IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
416+ IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
417+ IndentCaptureType::Align(_) => IndentScope::All,
418+ }
419+ }
420+}
421+/// This defines which part of a node an [IndentCapture] applies to.
422+/// Each [IndentCaptureType] has a default scope, but the scope can be changed
423+/// with `#set!` property declarations.
424+#[derive(Debug, Clone, Copy)]
425+enum IndentScope {
426+ /// The indent applies to the whole node
427+ All,
428+ /// The indent applies to everything except for the first line of the node
429+ Tail,
430+}
81
431
82− let not_first_sibling = node.next_sibling().is_some();
432+/// A capture from the indent query which does not define an indent but extends
83− let not_last_sibling = node.prev_sibling().is_some();
433+/// the range of a node. This is used before the indent is calculated.
84− let not_first_or_last_sibling = not_first_sibling && not_last_sibling;
434+#[derive(Debug)]
435+enum ExtendCapture {
436+ Extend,
437+ PreventOnce,
438+}
85
439
86− let parent_kind = parent.kind();
440+/// The result of running a tree-sitter indent query. This stores for
87− let is_scope = indent_scopes.iter().any(|scope| scope == &parent_kind);
441+/// each node (identified by its ID) the relevant captures (already filtered
442+/// by predicates).
443+#[derive(Debug)]
444+struct IndentQueryResult<'a> {
445+ indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
446+ extend_captures: HashMap<usize, Vec<ExtendCapture>>,
447+}
88
448
89− // && not_first_or_last_sibling
449+fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
90− if is_scope {
450+ let mut node_line = node.start_position().row;
91− increment += 1
451+ // Adjust for the new line that will be inserted
452+ if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
453+ node_line += 1;
92 }
454 }
455+ node_line
456+}
457+fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
458+ let mut node_line = node.end_position().row;
459+ // Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
460+ if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
461+ node_line += 1;
462+ }
463+ node_line
464+}
465+
466+fn query_indents<'a>(
467+ query: &Query,
468+ syntax: &Syntax,
469+ cursor: &mut QueryCursor,
470+ text: RopeSlice<'a>,
471+ range: std::ops::Range<usize>,
472+ new_line_byte_pos: Option<usize>,
473+) -> IndentQueryResult<'a> {
474+ let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
475+ let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
476+ cursor.set_byte_range(range);
477+
478+ // Iterate over all captures from the query
479+ for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
480+ // Skip matches where not all custom predicates are fulfilled
481+ if !query.general_predicates(m.pattern_index).iter().all(|pred| {
482+ match pred.operator.as_ref() {
483+ "not-kind-eq?" => match (pred.args.get(0), pred.args.get(1)) {
484+ (
485+ Some(QueryPredicateArg::Capture(capture_idx)),
486+ Some(QueryPredicateArg::String(kind)),
487+ ) => {
488+ let node = m.nodes_for_capture_index(*capture_idx).next();
489+ match node {
490+ Some(node) => node.kind()!=kind.as_ref(),
491+ _ => true,
492+ }
493+ }
494+ _ => {
495+ panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
496+ }
497+ },
498+ "same-line?" | "not-same-line?" => {
499+ match (pred.args.get(0), pred.args.get(1)) {
500+ (
501+ Some(QueryPredicateArg::Capture(capt1)),
502+ Some(QueryPredicateArg::Capture(capt2))
503+ ) => {
504+ let n1 = m.nodes_for_capture_index(*capt1).next();
505+ let n2 = m.nodes_for_capture_index(*capt2).next();
506+ match (n1, n2) {
507+ (Some(n1), Some(n2)) => {
508+ let n1_line = get_node_start_line(n1, new_line_byte_pos);
509+ let n2_line = get_node_start_line(n2, new_line_byte_pos);
510+ let same_line = n1_line == n2_line;
511+ same_line==(pred.operator.as_ref()=="same-line?")
512+ }
513+ _ => true,
514+ }
515+ }
516+ _ => {
517+ panic!("Invalid indent query: Arguments to \"{}\" must be 2 captures", pred.operator);
518+ }
519+ }
520+ }
521+ "one-line?" | "not-one-line?" => match pred.args.get(0) {
522+ Some(QueryPredicateArg::Capture(capture_idx)) => {
523+ let node = m.nodes_for_capture_index(*capture_idx).next();
93
524
94− // if last_scope && increment > 0 && ...{ ignore }
525+ match node {
526+ Some(node) => {
527+ let (start_line, end_line) = (get_node_start_line(node,new_line_byte_pos), get_node_end_line(node, new_line_byte_pos));
528+ let one_line = end_line == start_line;
529+ one_line != (pred.operator.as_ref() == "not-one-line?")
530+ },
531+ _ => true,
532+ }
533+ }
534+ _ => {
535+ panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
536+ }
537+ },
538+ _ => {
539+ panic!(
540+ "Invalid indent query: Unknown predicate (\"{}\")",
541+ pred.operator
542+ );
543+ }
544+ }
545+ }) {
546+ continue;
547+ }
548+ // A list of pairs (node_id, indent_capture) that are added by this match.
549+ // They cannot be added to indent_captures immediately since they may depend on other captures (such as an @anchor).
550+ let mut added_indent_captures: Vec<(usize, IndentCapture)> = Vec::new();
551+ // The row/column position of the optional anchor in this query
552+ let mut anchor: Option<tree_sitter::Node> = None;
553+ for capture in m.captures {
554+ let capture_name = query.capture_names()[capture.index as usize].as_str();
555+ let capture_type = match capture_name {
556+ "indent" => IndentCaptureType::Indent,
557+ "indent.always" => IndentCaptureType::IndentAlways,
558+ "outdent" => IndentCaptureType::Outdent,
559+ "outdent.always" => IndentCaptureType::OutdentAlways,
560+ // The alignment will be updated to the correct value at the end, when the anchor is known.
561+ "align" => IndentCaptureType::Align(RopeSlice::from("")),
562+ "anchor" => {
563+ if anchor.is_some() {
564+ log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
565+ } else {
566+ anchor = Some(capture.node);
567+ }
568+ continue;
569+ }
570+ "extend" => {
571+ extend_captures
572+ .entry(capture.node.id())
573+ .or_insert_with(|| Vec::with_capacity(1))
574+ .push(ExtendCapture::Extend);
575+ continue;
576+ }
577+ "extend.prevent-once" => {
578+ extend_captures
579+ .entry(capture.node.id())
580+ .or_insert_with(|| Vec::with_capacity(1))
581+ .push(ExtendCapture::PreventOnce);
582+ continue;
583+ }
584+ _ => {
585+ // Ignore any unknown captures (these may be needed for predicates such as #match?)
586+ continue;
587+ }
588+ };
589+ let scope = capture_type.default_scope();
590+ let mut indent_capture = IndentCapture {
591+ capture_type,
592+ scope,
593+ };
594+ // Apply additional settings for this capture
595+ for property in query.property_settings(m.pattern_index) {
596+ match property.key.as_ref() {
597+ "scope" => {
598+ indent_capture.scope = match property.value.as_deref() {
599+ Some("all") => IndentScope::All,
600+ Some("tail") => IndentScope::Tail,
601+ Some(s) => {
602+ panic!("Invalid indent query: Unknown value for \"scope\" property (\"{}\")", s);
603+ }
604+ None => {
605+ panic!(
606+ "Invalid indent query: Missing value for \"scope\" property"
607+ );
608+ }
609+ }
610+ }
611+ _ => {
612+ panic!(
613+ "Invalid indent query: Unknown property \"{}\"",
614+ property.key
615+ );
616+ }
617+ }
618+ }
619+ added_indent_captures.push((capture.node.id(), indent_capture))
620+ }
621+ for (node_id, mut capture) in added_indent_captures {
622+ // Set the anchor for all align queries.
623+ if let IndentCaptureType::Align(_) = capture.capture_type {
624+ let anchor = match anchor {
625+ None => {
626+ log::error!(
627+ "Invalid indent query: @align requires an accompanying @anchor."
628+ );
629+ continue;
630+ }
631+ Some(anchor) => anchor,
632+ };
633+ capture.capture_type = IndentCaptureType::Align(
634+ text.line(anchor.start_position().row)
635+ .byte_slice(0..anchor.start_position().column),
636+ );
637+ }
638+ indent_captures
639+ .entry(node_id)
640+ .or_insert_with(|| Vec::with_capacity(1))
641+ .push(capture);
642+ }
643+ }
95
644
96− walk(Some(parent)) + increment
645+ let result = IndentQueryResult {
646+ indent_captures,
647+ extend_captures,
648+ };
649+
650+ log::trace!("indent result = {:?}", result);
651+
652+ result
97}
653}
98
654
99−fn find_first_non_whitespace_char(state: &State, line_num: usize) -> usize {
655+/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
100− let line = state.doc.line(line_num);
656+/// Any ancestor of deepest_preceding which is also a descendant of node may be "extended". In that case, node will be updated,
101− let mut start = state.doc.line_to_char(line_num);
657+/// so that the indent computation starts with the correct syntax node.
658+fn extend_nodes<'a>(
659+ node: &mut Node<'a>,
660+ mut deepest_preceding: Node<'a>,
661+ extend_captures: &HashMap<usize, Vec<ExtendCapture>>,
662+ text: RopeSlice,
663+ line: usize,
664+ tab_width: usize,
665+ indent_width: usize,
666+) {
667+ let mut stop_extend = false;
102
668
103− // find first non-whitespace char
669+ while deepest_preceding != *node {
104− for ch in line.chars() {
670+ let mut extend_node = false;
105− // TODO: could use memchr with chunks?
671+ // This will be set to true if this node is captured, regardless of whether
106− if ch != ' ' && ch != '\t' {
672+ // it actually will be extended (e.g. because the cursor isn't indented
673+ // more than the node).
674+ let mut node_captured = false;
675+ if let Some(captures) = extend_captures.get(&deepest_preceding.id()) {
676+ for capture in captures {
677+ match capture {
678+ ExtendCapture::PreventOnce => {
679+ stop_extend = true;
680+ }
681+ ExtendCapture::Extend => {
682+ node_captured = true;
683+ // We extend the node if
684+ // - the cursor is on the same line as the end of the node OR
685+ // - the line that the cursor is on is more indented than the
686+ // first line of the node
687+ if deepest_preceding.end_position().row == line {
688+ extend_node = true;
689+ } else {
690+ let cursor_indent =
691+ indent_level_for_line(text.line(line), tab_width, indent_width);
692+ let node_indent = indent_level_for_line(
693+ text.line(deepest_preceding.start_position().row),
694+ tab_width,
695+ indent_width,
696+ );
697+ if cursor_indent > node_indent {
698+ extend_node = true;
699+ }
700+ }
701+ }
702+ }
703+ }
704+ }
705+ // If we encountered some `StopExtend` capture before, we don't
706+ // extend the node even if we otherwise would
707+ if node_captured && stop_extend {
708+ stop_extend = false;
709+ } else if extend_node && !stop_extend {
710+ *node = deepest_preceding;
107 break;
711 break;
108 }
712 }
109− start += 1;
713+ // If the tree contains a syntax error, `deepest_preceding` may not
714+ // have a parent despite being a descendant of `node`.
715+ deepest_preceding = match deepest_preceding.parent() {
716+ Some(parent) => parent,
717+ None => return,
718+ }
110 }
719 }
111− start
112}
720}
113
721
114−fn suggested_indent_for_line(syntax: Option<&Syntax>, state: &State, line_num: usize) -> usize {
722+/// Prepare an indent query by computing:
115− let line = state.doc.line(line_num);
723+/// - The node from which to start the query (this is non-trivial due to `@extend` captures)
116− let current = indent_level_for_line(line);
724+/// - The indent captures for all relevant nodes.
725+#[allow(clippy::too_many_arguments)]
726+fn init_indent_query<'a, 'b>(
727+ query: &Query,
728+ syntax: &'a Syntax,
729+ text: RopeSlice<'b>,
730+ tab_width: usize,
731+ indent_width: usize,
732+ line: usize,
733+ byte_pos: usize,
734+ new_line_byte_pos: Option<usize>,
735+) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
736+ // The innermost tree-sitter node which is considered for the indent
737+ // computation. It may change if some predeceding node is extended
738+ let mut node = syntax
739+ .tree()
740+ .root_node()
741+ .descendant_for_byte_range(byte_pos, byte_pos)?;
117
742
118− let start = find_first_non_whitespace_char(state, line_num);
743+ let (query_result, deepest_preceding) = {
744+ // The query range should intersect with all nodes directly preceding
745+ // the position of the indent query in case one of them is extended.
746+ let mut deepest_preceding = None; // The deepest node preceding the indent query position
747+ let mut tree_cursor = node.walk();
748+ for child in node.children(&mut tree_cursor) {
749+ if child.byte_range().end <= byte_pos {
750+ deepest_preceding = Some(child);
751+ }
752+ }
753+ deepest_preceding = deepest_preceding.map(|mut prec| {
754+ // Get the deepest directly preceding node
755+ while prec.child_count() > 0 {
756+ prec = prec.child(prec.child_count() - 1).unwrap();
757+ }
758+ prec
759+ });
760+ let query_range = deepest_preceding
761+ .map(|prec| prec.byte_range().end - 1..byte_pos + 1)
762+ .unwrap_or(byte_pos..byte_pos + 1);
763+
764+ crate::syntax::PARSER.with(|ts_parser| {
765+ let mut ts_parser = ts_parser.borrow_mut();
766+ let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
767+ let query_result = query_indents(
768+ query,
769+ syntax,
770+ &mut cursor,
771+ text,
772+ query_range,
773+ new_line_byte_pos,
774+ );
775+ ts_parser.cursors.push(cursor);
776+ (query_result, deepest_preceding)
777+ })
778+ };
779+ let extend_captures = query_result.extend_captures;
119
780
120− suggested_indent_for_pos(syntax, state, start)
781+ // Check for extend captures, potentially changing the node that the indent calculation starts with
782+ if let Some(deepest_preceding) = deepest_preceding {
783+ extend_nodes(
784+ &mut node,
785+ deepest_preceding,
786+ &extend_captures,
787+ text,
788+ line,
789+ tab_width,
790+ indent_width,
791+ );
792+ }
793+ Some((node, query_result.indent_captures))
121}
794}
122
795
123−pub fn suggested_indent_for_pos(syntax: Option<&Syntax>, state: &State, pos: usize) -> usize {
796+/// Use the syntax tree to determine the indentation for a given position.
124− if let Some(syntax) = syntax {
797+/// This can be used in 2 ways:
798+///
799+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
800+/// - In this case, pos should be inside the first tree-sitter node on that line.
801+/// In most cases, this can just be the first non-whitespace on that line.
802+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
803+/// after pos were moved to a new line.
804+///
805+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
806+/// Each of these nodes produces some [Indentation] for:
807+///
808+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
809+/// - The line after the node. This is defined by:
810+/// - The scope `tail`.
811+/// - The scope `all` if this node is not the first node on its line.
812+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
813+/// The indents from different nodes for the same line are then combined.
814+/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
815+///
816+/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
817+/// ```ignore
818+/// some_function(|| {
819+/// // Both the function parameters as well as the contained block should be indented.
820+/// // Because they are on the same line, this only yields one indent level
821+/// });
822+/// ```
823+///
824+/// ```ignore
825+/// some_function(
826+/// param1,
827+/// || {
828+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
829+/// },
830+/// );
831+/// ```
832+#[allow(clippy::too_many_arguments)]
833+pub fn treesitter_indent_for_pos<'a>(
834+ query: &Query,
835+ syntax: &Syntax,
836+ tab_width: usize,
837+ indent_width: usize,
838+ text: RopeSlice<'a>,
839+ line: usize,
840+ pos: usize,
841+ new_line: bool,
842+) -> Option<Indentation<'a>> {
125− let byte_start = state.doc.char_to_byte(pos);
843+ let byte_pos = text.char_to_byte(pos);
126− let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
844+ let new_line_byte_pos = new_line.then_some(byte_pos);
845+ let (mut node, mut indent_captures) = init_indent_query(
846+ query,
847+ syntax,
848+ text,
849+ tab_width,
850+ indent_width,
851+ line,
852+ byte_pos,
853+ new_line_byte_pos,
854+ )?;
855+ let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
127
856
128− let indentation = walk(node);
857+ let mut result = Indentation::default();
129− // special case for comments
858+ // We always keep track of all the indent changes on one line, in order to only indent once
859+ // even if there are multiple "indent" nodes on the same line
860+ let mut indent_for_line = Indentation::default();
861+ let mut indent_for_line_below = Indentation::default();
130
862
131− // if preserve_leading_whitespace
863+ loop {
864+ // This can safely be unwrapped because `first_in_line` contains
865+ // one entry for each ancestor of the node (which is what we iterate over)
866+ let is_first = *first_in_line.last().unwrap();
132
867
133− indentation
868+ // Apply all indent definitions for this node.
869+ // Since we only iterate over each node once, we can remove the
870+ // corresponding captures from the HashMap to avoid cloning them.
871+ if let Some(definitions) = indent_captures.remove(&node.id()) {
872+ for definition in definitions {
873+ match definition.scope {
874+ IndentScope::All => {
875+ if is_first {
876+ indent_for_line.add_capture(definition.capture_type);
134− } else {
877+ } else {
135− // TODO: heuristics for non-tree sitter grammars
878+ indent_for_line_below.add_capture(definition.capture_type);
136− 0
879+ }
880+ }
881+ IndentScope::Tail => {
882+ indent_for_line_below.add_capture(definition.capture_type);
883+ }
884+ }
885+ }
886+ }
887+
888+ if let Some(parent) = node.parent() {
889+ let node_line = get_node_start_line(node, new_line_byte_pos);
890+ let parent_line = get_node_start_line(parent, new_line_byte_pos);
891+
892+ if node_line != parent_line {
893+ // Don't add indent for the line below the line of the query
894+ if node_line < line + (new_line as usize) {
895+ result.add_line(indent_for_line_below);
896+ }
897+
898+ if node_line == parent_line + 1 {
899+ indent_for_line_below = indent_for_line;
900+ } else {
901+ result.add_line(indent_for_line);
902+ indent_for_line_below = Indentation::default();
903+ }
904+
905+ indent_for_line = Indentation::default();
906+ }
907+
908+ node = parent;
909+ first_in_line.pop();
910+ } else {
911+ // Only add the indentation for the line below if that line
912+ // is not after the line that the indentation is calculated for.
913+ if (node.start_position().row < line)
914+ || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
915+ {
916+ result.add_line(indent_for_line_below);
917+ }
918+ result.add_line(indent_for_line);
919+ break;
920+ }
921+ }
922+ Some(result)
923+}
924+
925+/// Returns the indentation for a new line.
926+/// This is done either using treesitter, or if that's not available by copying the indentation from the current line
927+#[allow(clippy::too_many_arguments)]
928+pub fn indent_for_newline(
929+ language_config: Option<&LanguageConfiguration>,
930+ syntax: Option<&Syntax>,
931+ indent_heuristic: &IndentationHeuristic,
932+ indent_style: &IndentStyle,
933+ tab_width: usize,
934+ text: RopeSlice,
935+ line_before: usize,
936+ line_before_end_pos: usize,
937+ current_line: usize,
938+) -> String {
939+ let indent_width = indent_style.indent_width(tab_width);
940+ if let (
941+ IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942+ Some(query),
943+ Some(syntax),
944+ ) = (
945+ indent_heuristic,
946+ language_config.and_then(|config| config.indent_query()),
947+ syntax,
948+ ) {
949+ if let Some(indent) = treesitter_indent_for_pos(
950+ query,
951+ syntax,
952+ tab_width,
953+ indent_width,
954+ text,
955+ line_before,
956+ line_before_end_pos,
957+ true,
958+ ) {
959+ if *indent_heuristic == IndentationHeuristic::Hybrid {
960+ // We want to compute the indentation not only based on the
961+ // syntax tree but also on the actual indentation of a previous
962+ // line. This makes indentation computation more resilient to
963+ // incomplete queries, incomplete source code & differing indentation
964+ // styles for the same language.
965+ // However, using the indent of a previous line as a baseline may not
966+ // make sense, e.g. if it has a different alignment than the new line.
967+ // In order to prevent edge cases with long running times, we only try
968+ // a constant number of (non-empty) lines.
969+ const MAX_ATTEMPTS: usize = 4;
970+ let mut num_attempts = 0;
971+ for line_idx in (0..=line_before).rev() {
972+ let line = text.line(line_idx);
973+ let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
974+ Some(i) => i,
975+ None => {
976+ continue;
977+ }
978+ };
979+ if let Some(indent) = (|| {
980+ let computed_indent = treesitter_indent_for_pos(
981+ query,
982+ syntax,
983+ tab_width,
984+ indent_width,
985+ text,
986+ line_idx,
987+ text.line_to_char(line_idx) + first_non_whitespace_char,
988+ false,
989+ )?;
990+ let leading_whitespace = line.slice(0..first_non_whitespace_char);
991+ indent.relative_indent(
992+ &computed_indent,
993+ leading_whitespace,
994+ indent_style,
995+ tab_width,
996+ )
997+ })() {
998+ return indent;
999+ }
1000+ num_attempts += 1;
1001+ if num_attempts == MAX_ATTEMPTS {
1002+ break;
1003+ }
1004+ }
1005+ }
1006+ return indent.to_string(indent_style, tab_width);
1007+ };
1008+ }
1009+ // Fallback in case we either don't have indent queries or they failed for some reason
1010+ let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
1011+ indent_style.as_str().repeat(indent_level)
1012+}
1013+
1014+pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
1015+ let mut scopes = Vec::new();
1016+ if let Some(syntax) = syntax {
1017+ let pos = text.char_to_byte(pos);
1018+ let mut node = match syntax
1019+ .tree()
1020+ .root_node()
1021+ .descendant_for_byte_range(pos, pos)
1022+ {
1023+ Some(node) => node,
1024+ None => return scopes,
1025+ };
1026+
1027+ scopes.push(node.kind());
1028+
1029+ while let Some(parent) = node.parent() {
1030+ scopes.push(parent.kind());
1031+ node = parent;
1032+ }
137 }
1033 }
1034+
1035+ scopes.reverse();
1036+ scopes
138}
1037}
139
1038
140#[cfg(test)]
1039#[cfg(test)]
141mod test {
1040mod test {
142 use super::*;
1041 use super::*;
1042+ use crate::Rope;
143
1043
144 #[test]
1044 #[test]
145 fn test_indent_level() {
1045 fn test_indent_level() {
1046+ let tab_width = 4;
1047+ let indent_width = 4;
146 let line = Rope::from(" fn new"); // 8 spaces
1048 let line = Rope::from(" fn new"); // 8 spaces
1049+ assert_eq!(
147− assert_eq!(indent_level_for_line(line.slice(..)), 2);
1050+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1051+ 2
1052+ );
148 let line = Rope::from("\t\t\tfn new"); // 3 tabs
1053 let line = Rope::from("\t\t\tfn new"); // 3 tabs
1054+ assert_eq!(
149− assert_eq!(indent_level_for_line(line.slice(..)), 3);
1055+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1056+ 3
1057+ );
150 // mixed indentation
1058 // mixed indentation
151 let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
1059 let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
1060+ assert_eq!(
152− assert_eq!(indent_level_for_line(line.slice(..)), 3);
1061+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1062+ 3
1063+ );
153 }
1064 }
154
1065
155 #[test]
1066 #[test]
156− fn test_suggested_indent_for_line() {
1067+ fn test_large_indent_level() {
157− let doc = Rope::from(
1068+ let tab_width = 16;
158− "mod test {
1069+ let indent_width = 16;
159− fn hello_world() {
1070+ let line = Rope::from(" fn new"); // 16 spaces
160− 1 + 1
1071+ assert_eq!(
1072+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1073+ 1
1074+ );
1075+ let line = Rope::from(" fn new"); // 32 spaces
1076+ assert_eq!(
1077+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1078+ 2
1079+ );
161 }
1080 }
1081+
1082+ #[test]
1083+ fn add_capture() {
1084+ let indent = || Indentation {
1085+ indent: 1,
1086+ ..Default::default()
1087+ };
1088+ let indent_always = || Indentation {
1089+ indent_always: 1,
1090+ ..Default::default()
162−}
1091+ };
163−",
1092+ let outdent = || Indentation {
1093+ outdent: 1,
1094+ ..Default::default()
1095+ };
1096+ let outdent_always = || Indentation {
1097+ outdent_always: 1,
1098+ ..Default::default()
1099+ };
1100+
1101+ fn add_capture<'a>(
1102+ mut indent: Indentation<'a>,
1103+ capture: IndentCaptureType<'a>,
1104+ ) -> Indentation<'a> {
1105+ indent.add_capture(capture);
1106+ indent
1107+ }
1108+
1109+ // adding an indent to no indent makes an indent
1110+ assert_eq!(
1111+ indent(),
1112+ add_capture(Indentation::default(), IndentCaptureType::Indent)
1113+ );
1114+ assert_eq!(
1115+ indent_always(),
1116+ add_capture(Indentation::default(), IndentCaptureType::IndentAlways)
1117+ );
1118+ assert_eq!(
1119+ outdent(),
1120+ add_capture(Indentation::default(), IndentCaptureType::Outdent)
1121+ );
1122+ assert_eq!(
1123+ outdent_always(),
1124+ add_capture(Indentation::default(), IndentCaptureType::OutdentAlways)
1125+ );
1126+
1127+ // adding an indent to an already indented has no effect
1128+ assert_eq!(indent(), add_capture(indent(), IndentCaptureType::Indent));
1129+ assert_eq!(
1130+ outdent(),
1131+ add_capture(outdent(), IndentCaptureType::Outdent)
164 );
1132 );
165
1133
166− let state = State::new(doc);
1134+ // adding an always to a regular makes it always
167− // TODO: set_language
1135+ assert_eq!(
168− let language_config = crate::syntax::LOADER
1136+ indent_always(),
169− .language_config_for_scope("source.rust")
1137+ add_capture(indent(), IndentCaptureType::IndentAlways)
170− .unwrap();
1138+ );
171− let highlight_config = language_config.highlight_config(&[]).unwrap().unwrap();
1139+ assert_eq!(
172− let syntax = Syntax::new(&state.doc, highlight_config.clone());
1140+ outdent_always(),
1141+ add_capture(outdent(), IndentCaptureType::OutdentAlways)
1142+ );
1143+
1144+ // adding an always to an always is additive
1145+ assert_eq!(
1146+ Indentation {
1147+ indent_always: 2,
1148+ ..Default::default()
1149+ },
1150+ add_capture(indent_always(), IndentCaptureType::IndentAlways)
1151+ );
1152+ assert_eq!(
1153+ Indentation {
1154+ outdent_always: 2,
1155+ ..Default::default()
1156+ },
1157+ add_capture(outdent_always(), IndentCaptureType::OutdentAlways)
1158+ );
173
1159
174− assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 0), 0); // mod
1160+ // adding regular to always should be associative
175− assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 1), 1); // fn
1161+ assert_eq!(
176− assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 2), 2); // 1 + 1
1162+ Indentation {
177− assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 4), 1); // }
1163+ indent_always: 1,
178− assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 5), 0); // }
1164+ ..Default::default()
1165+ },
1166+ add_capture(
1167+ add_capture(indent(), IndentCaptureType::Indent),
1168+ IndentCaptureType::IndentAlways
1169+ )
1170+ );
1171+ assert_eq!(
1172+ Indentation {
1173+ indent_always: 1,
1174+ ..Default::default()
1175+ },
1176+ add_capture(
1177+ add_capture(indent(), IndentCaptureType::IndentAlways),
1178+ IndentCaptureType::Indent
1179+ )
1180+ );
1181+ assert_eq!(
1182+ Indentation {
1183+ outdent_always: 1,
1184+ ..Default::default()
1185+ },
1186+ add_capture(
1187+ add_capture(outdent(), IndentCaptureType::Outdent),
1188+ IndentCaptureType::OutdentAlways
1189+ )
1190+ );
1191+ assert_eq!(
1192+ Indentation {
1193+ outdent_always: 1,
1194+ ..Default::default()
1195+ },
1196+ add_capture(
1197+ add_capture(outdent(), IndentCaptureType::OutdentAlways),
1198+ IndentCaptureType::Outdent
1199+ )
1200+ );
1201+ }
1202+
1203+ #[test]
1204+ fn test_relative_indent() {
1205+ let indent_style = IndentStyle::Spaces(4);
1206+ let tab_width: usize = 4;
1207+ let no_align = [
1208+ Indentation::default(),
1209+ Indentation {
1210+ indent: 1,
1211+ ..Default::default()
1212+ },
1213+ Indentation {
1214+ indent: 5,
1215+ outdent: 1,
1216+ ..Default::default()
1217+ },
1218+ ];
1219+ let align = no_align.clone().map(|indent| Indentation {
1220+ align: Some(RopeSlice::from("12345")),
1221+ ..indent
1222+ });
1223+ let different_align = Indentation {
1224+ align: Some(RopeSlice::from("123456")),
1225+ ..Default::default()
1226+ };
1227+
1228+ // Check that relative and absolute indentation computation are the same when the line we compare to is
1229+ // indented as we expect.
1230+ let check_consistency = |indent: &Indentation, other: &Indentation| {
1231+ assert_eq!(
1232+ indent.relative_indent(
1233+ other,
1234+ RopeSlice::from(other.to_string(&indent_style, tab_width).as_str()),
1235+ &indent_style,
1236+ tab_width
1237+ ),
1238+ Some(indent.to_string(&indent_style, tab_width))
1239+ );
1240+ };
1241+ for a in &no_align {
1242+ for b in &no_align {
1243+ check_consistency(a, b);
1244+ }
1245+ }
1246+ for a in &align {
1247+ for b in &align {
1248+ check_consistency(a, b);
1249+ }
1250+ }
1251+
1252+ // Relative indent computation makes no sense if the alignment differs
1253+ assert_eq!(
1254+ align[0].relative_indent(
1255+ &no_align[0],
1256+ RopeSlice::from(" "),
1257+ &indent_style,
1258+ tab_width
1259+ ),
1260+ None
1261+ );
1262+ assert_eq!(
1263+ align[0].relative_indent(
1264+ &different_align,
1265+ RopeSlice::from(" "),
1266+ &indent_style,
1267+ tab_width
1268+ ),
1269+ None
1270+ );
179 }
1271 }
180}
1272}
helix-core/src/lib.rs+55−13
@@ -1,30 +1,72 @@
1−#![allow(unused)]
1+pub use encoding_rs as encoding;
2+
3+pub mod auto_pairs;
4+pub mod chars;
5+pub mod comment;
6+pub mod config;
2−mod diagnostic;
7+pub mod diagnostic;
8+pub mod diff;
9+pub mod doc_formatter;
10+pub mod fuzzy;
3pub mod graphemes;
11pub mod graphemes;
4−mod history;
12+pub mod history;
13+pub mod increment;
5pub mod indent;
14pub mod indent;
15+pub mod line_ending;
6pub mod macros;
16pub mod macros;
17+pub mod match_brackets;
18+pub mod movement;
19+pub mod object;
20+pub mod path;
7mod position;
21mod position;
8−pub mod register;
22+pub mod search;
9pub mod selection;
23pub mod selection;
10−pub mod state;
24+pub mod shellwords;
25+pub mod surround;
11pub mod syntax;
26pub mod syntax;
27+pub mod test;
28+pub mod text_annotations;
29+pub mod textobject;
12mod transaction;
30mod transaction;
31+pub mod wrap;
32+
33+pub mod unicode {
34+ pub use unicode_general_category as category;
35+ pub use unicode_segmentation as segmentation;
36+ pub use unicode_width as width;
37+}
38+
39+pub use helix_loader::find_workspace;
13
40
14−pub use ropey::{Rope, RopeSlice};
41+pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
42+ line.chars().position(|ch| !ch.is_whitespace())
43+}
44+mod rope_reader;
15
45
46+pub use rope_reader::RopeReader;
47+pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
48+
16−pub use tendril::StrTendril as Tendril;
49+// pub use tendril::StrTendril as Tendril;
50+pub use smartstring::SmartString;
51+
52+pub type Tendril = SmartString<smartstring::LazyCompact>;
17
53
18#[doc(inline)]
54#[doc(inline)]
19pub use {regex, tree_sitter};
55pub use {regex, tree_sitter};
20
56
57+pub use graphemes::RopeGraphemes;
21−pub use position::Position;
58+pub use position::{
59+ char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
60+ visual_offset_from_block, Position, VisualOffsetError,
61+};
62+#[allow(deprecated)]
63+pub use position::{pos_at_visual_coords, visual_coords_at_pos};
64+
22−pub use selection::Range;
65+pub use selection::{Range, Selection};
23−pub use selection::Selection;
66+pub use smallvec::{smallvec, SmallVec};
24pub use syntax::Syntax;
67pub use syntax::Syntax;
25
68
26pub use diagnostic::Diagnostic;
69pub use diagnostic::Diagnostic;
27−pub use history::History;
28−pub use state::State;
29
70
71+pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
30−pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
72+pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
helix-core/src/line_ending.rsnew file+328
@@ -0,0 +1,328 @@
1+use crate::{Rope, RopeSlice};
2+
3+#[cfg(target_os = "windows")]
4+pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::Crlf;
5+#[cfg(not(target_os = "windows"))]
6+pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF;
7+
8+/// Represents one of the valid Unicode line endings.
9+#[derive(PartialEq, Eq, Copy, Clone, Debug)]
10+pub enum LineEnding {
11+ Crlf, // CarriageReturn followed by LineFeed
12+ LF, // U+000A -- LineFeed
13+ #[cfg(feature = "unicode-lines")]
14+ VT, // U+000B -- VerticalTab
15+ #[cfg(feature = "unicode-lines")]
16+ FF, // U+000C -- FormFeed
17+ #[cfg(feature = "unicode-lines")]
18+ CR, // U+000D -- CarriageReturn
19+ #[cfg(feature = "unicode-lines")]
20+ Nel, // U+0085 -- NextLine
21+ #[cfg(feature = "unicode-lines")]
22+ LS, // U+2028 -- Line Separator
23+ #[cfg(feature = "unicode-lines")]
24+ PS, // U+2029 -- ParagraphSeparator
25+}
26+
27+impl LineEnding {
28+ #[inline]
29+ pub const fn len_chars(&self) -> usize {
30+ match self {
31+ Self::Crlf => 2,
32+ _ => 1,
33+ }
34+ }
35+
36+ #[inline]
37+ pub const fn as_str(&self) -> &'static str {
38+ match self {
39+ Self::Crlf => "\u{000D}\u{000A}",
40+ Self::LF => "\u{000A}",
41+ #[cfg(feature = "unicode-lines")]
42+ Self::VT => "\u{000B}",
43+ #[cfg(feature = "unicode-lines")]
44+ Self::FF => "\u{000C}",
45+ #[cfg(feature = "unicode-lines")]
46+ Self::CR => "\u{000D}",
47+ #[cfg(feature = "unicode-lines")]
48+ Self::Nel => "\u{0085}",
49+ #[cfg(feature = "unicode-lines")]
50+ Self::LS => "\u{2028}",
51+ #[cfg(feature = "unicode-lines")]
52+ Self::PS => "\u{2029}",
53+ }
54+ }
55+
56+ #[inline]
57+ pub const fn from_char(ch: char) -> Option<LineEnding> {
58+ match ch {
59+ '\u{000A}' => Some(LineEnding::LF),
60+ #[cfg(feature = "unicode-lines")]
61+ '\u{000B}' => Some(LineEnding::VT),
62+ #[cfg(feature = "unicode-lines")]
63+ '\u{000C}' => Some(LineEnding::FF),
64+ #[cfg(feature = "unicode-lines")]
65+ '\u{000D}' => Some(LineEnding::CR),
66+ #[cfg(feature = "unicode-lines")]
67+ '\u{0085}' => Some(LineEnding::Nel),
68+ #[cfg(feature = "unicode-lines")]
69+ '\u{2028}' => Some(LineEnding::LS),
70+ #[cfg(feature = "unicode-lines")]
71+ '\u{2029}' => Some(LineEnding::PS),
72+ // Not a line ending
73+ _ => None,
74+ }
75+ }
76+
77+ // Normally we'd want to implement the FromStr trait, but in this case
78+ // that would force us into a different return type than from_char or
79+ // or from_rope_slice, which would be weird.
80+ #[allow(clippy::should_implement_trait)]
81+ #[inline]
82+ pub fn from_str(g: &str) -> Option<LineEnding> {
83+ match g {
84+ "\u{000D}\u{000A}" => Some(LineEnding::Crlf),
85+ "\u{000A}" => Some(LineEnding::LF),
86+ #[cfg(feature = "unicode-lines")]
87+ "\u{000B}" => Some(LineEnding::VT),
88+ #[cfg(feature = "unicode-lines")]
89+ "\u{000C}" => Some(LineEnding::FF),
90+ #[cfg(feature = "unicode-lines")]
91+ "\u{000D}" => Some(LineEnding::CR),
92+ #[cfg(feature = "unicode-lines")]
93+ "\u{0085}" => Some(LineEnding::Nel),
94+ #[cfg(feature = "unicode-lines")]
95+ "\u{2028}" => Some(LineEnding::LS),
96+ #[cfg(feature = "unicode-lines")]
97+ "\u{2029}" => Some(LineEnding::PS),
98+ // Not a line ending
99+ _ => None,
100+ }
101+ }
102+
103+ #[inline]
104+ pub fn from_rope_slice(g: &RopeSlice) -> Option<LineEnding> {
105+ if let Some(text) = g.as_str() {
106+ LineEnding::from_str(text)
107+ } else {
108+ // Non-contiguous, so it can't be a line ending.
109+ // Specifically, Ropey guarantees that CRLF is always
110+ // contiguous. And the remaining line endings are all
111+ // single `char`s, and therefore trivially contiguous.
112+ None
113+ }
114+ }
115+}
116+
117+#[inline]
118+pub fn str_is_line_ending(s: &str) -> bool {
119+ LineEnding::from_str(s).is_some()
120+}
121+
122+#[inline]
123+pub fn rope_is_line_ending(r: RopeSlice) -> bool {
124+ r.chunks().all(str_is_line_ending)
125+}
126+
127+/// Attempts to detect what line ending the passed document uses.
128+pub fn auto_detect_line_ending(doc: &Rope) -> Option<LineEnding> {
129+ // Return first matched line ending. Not all possible line endings
130+ // are being matched, as they might be special-use only
131+ for line in doc.lines().take(100) {
132+ match get_line_ending(&line) {
133+ None => {}
134+ #[cfg(feature = "unicode-lines")]
135+ Some(LineEnding::VT) | Some(LineEnding::FF) | Some(LineEnding::PS) => {}
136+ ending => return ending,
137+ }
138+ }
139+ None
140+}
141+
142+/// Returns the passed line's line ending, if any.
143+pub fn get_line_ending(line: &RopeSlice) -> Option<LineEnding> {
144+ // Last character as str.
145+ let g1 = line
146+ .slice(line.len_chars().saturating_sub(1)..)
147+ .as_str()
148+ .unwrap();
149+
150+ // Last two characters as str, or empty str if they're not contiguous.
151+ // It's fine to punt on the non-contiguous case, because Ropey guarantees
152+ // that CRLF is always contiguous.
153+ let g2 = line
154+ .slice(line.len_chars().saturating_sub(2)..)
155+ .as_str()
156+ .unwrap_or("");
157+
158+ // First check the two-character case for CRLF, then check the single-character case.
159+ LineEnding::from_str(g2).or_else(|| LineEnding::from_str(g1))
160+}
161+
162+#[cfg(not(feature = "unicode-lines"))]
163+/// Returns the passed line's line ending, if any.
164+pub fn get_line_ending_of_str(line: &str) -> Option<LineEnding> {
165+ if line.ends_with("\u{000D}\u{000A}") {
166+ Some(LineEnding::Crlf)
167+ } else if line.ends_with('\u{000A}') {
168+ Some(LineEnding::LF)
169+ } else {
170+ None
171+ }
172+}
173+
174+#[cfg(feature = "unicode-lines")]
175+/// Returns the passed line's line ending, if any.
176+pub fn get_line_ending_of_str(line: &str) -> Option<LineEnding> {
177+ if line.ends_with("\u{000D}\u{000A}") {
178+ Some(LineEnding::Crlf)
179+ } else if line.ends_with('\u{000A}') {
180+ Some(LineEnding::LF)
181+ } else if line.ends_with('\u{000B}') {
182+ Some(LineEnding::VT)
183+ } else if line.ends_with('\u{000C}') {
184+ Some(LineEnding::FF)
185+ } else if line.ends_with('\u{000D}') {
186+ Some(LineEnding::CR)
187+ } else if line.ends_with('\u{0085}') {
188+ Some(LineEnding::Nel)
189+ } else if line.ends_with('\u{2028}') {
190+ Some(LineEnding::LS)
191+ } else if line.ends_with('\u{2029}') {
192+ Some(LineEnding::PS)
193+ } else {
194+ None
195+ }
196+}
197+
198+/// Returns the char index of the end of the given line, not including its line ending.
199+pub fn line_end_char_index(slice: &RopeSlice, line: usize) -> usize {
200+ slice.line_to_char(line + 1)
201+ - get_line_ending(&slice.line(line))
202+ .map(|le| le.len_chars())
203+ .unwrap_or(0)
204+}
205+
206+pub fn line_end_byte_index(slice: &RopeSlice, line: usize) -> usize {
207+ slice.line_to_byte(line + 1)
208+ - get_line_ending(&slice.line(line))
209+ .map(|le| le.as_str().len())
210+ .unwrap_or(0)
211+}
212+
213+/// Fetches line `line_idx` from the passed rope slice, sans any line ending.
214+pub fn line_without_line_ending<'a>(slice: &'a RopeSlice, line_idx: usize) -> RopeSlice<'a> {
215+ let start = slice.line_to_char(line_idx);
216+ let end = line_end_char_index(slice, line_idx);
217+ slice.slice(start..end)
218+}
219+
220+/// Returns the char index of the end of the given RopeSlice, not including
221+/// any final line ending.
222+pub fn rope_end_without_line_ending(slice: &RopeSlice) -> usize {
223+ slice.len_chars() - get_line_ending(slice).map(|le| le.len_chars()).unwrap_or(0)
224+}
225+
226+#[cfg(test)]
227+mod line_ending_tests {
228+ use super::*;
229+
230+ #[test]
231+ fn line_ending_autodetect() {
232+ assert_eq!(
233+ auto_detect_line_ending(&Rope::from_str("\n")),
234+ Some(LineEnding::LF)
235+ );
236+ assert_eq!(
237+ auto_detect_line_ending(&Rope::from_str("\r\n")),
238+ Some(LineEnding::Crlf)
239+ );
240+ assert_eq!(auto_detect_line_ending(&Rope::from_str("hello")), None);
241+ assert_eq!(auto_detect_line_ending(&Rope::from_str("")), None);
242+ assert_eq!(
243+ auto_detect_line_ending(&Rope::from_str("hello\nhelix\r\n")),
244+ Some(LineEnding::LF)
245+ );
246+ assert_eq!(
247+ auto_detect_line_ending(&Rope::from_str("a formfeed\u{000C}")),
248+ None
249+ );
250+ assert_eq!(
251+ auto_detect_line_ending(&Rope::from_str("\n\u{000A}\n \u{000A}")),
252+ Some(LineEnding::LF)
253+ );
254+ assert_eq!(
255+ auto_detect_line_ending(&Rope::from_str(
256+ "a formfeed\u{000C} with a\u{000C} linefeed\u{000A}"
257+ )),
258+ Some(LineEnding::LF)
259+ );
260+ assert_eq!(auto_detect_line_ending(&Rope::from_str("a formfeed\u{000C} with a\u{000C} carriage return linefeed\u{000D}\u{000A} and a linefeed\u{000A}")), Some(LineEnding::Crlf));
261+ }
262+
263+ #[test]
264+ fn str_to_line_ending() {
265+ #[cfg(feature = "unicode-lines")]
266+ assert_eq!(LineEnding::from_str("\r"), Some(LineEnding::CR));
267+ assert_eq!(LineEnding::from_str("\n"), Some(LineEnding::LF));
268+ assert_eq!(LineEnding::from_str("\r\n"), Some(LineEnding::Crlf));
269+ assert_eq!(LineEnding::from_str("hello\n"), None);
270+ }
271+
272+ #[test]
273+ fn rope_slice_to_line_ending() {
274+ let r = Rope::from_str("hello\r\n");
275+ #[cfg(feature = "unicode-lines")]
276+ assert_eq!(
277+ LineEnding::from_rope_slice(&r.slice(5..6)),
278+ Some(LineEnding::CR)
279+ );
280+ assert_eq!(
281+ LineEnding::from_rope_slice(&r.slice(6..7)),
282+ Some(LineEnding::LF)
283+ );
284+ assert_eq!(
285+ LineEnding::from_rope_slice(&r.slice(5..7)),
286+ Some(LineEnding::Crlf)
287+ );
288+ assert_eq!(LineEnding::from_rope_slice(&r.slice(..)), None);
289+ }
290+
291+ #[test]
292+ fn get_line_ending_rope_slice() {
293+ let r = Rope::from_str("Hello\rworld\nhow\r\nare you?");
294+ #[cfg(feature = "unicode-lines")]
295+ assert_eq!(get_line_ending(&r.slice(..6)), Some(LineEnding::CR));
296+ assert_eq!(get_line_ending(&r.slice(..12)), Some(LineEnding::LF));
297+ assert_eq!(get_line_ending(&r.slice(..17)), Some(LineEnding::Crlf));
298+ assert_eq!(get_line_ending(&r.slice(..)), None);
299+ }
300+
301+ #[test]
302+ fn get_line_ending_str() {
303+ let text = "Hello\rworld\nhow\r\nare you?";
304+ #[cfg(feature = "unicode-lines")]
305+ assert_eq!(get_line_ending_of_str(&text[..6]), Some(LineEnding::CR));
306+ assert_eq!(get_line_ending_of_str(&text[..12]), Some(LineEnding::LF));
307+ assert_eq!(get_line_ending_of_str(&text[..17]), Some(LineEnding::Crlf));
308+ assert_eq!(get_line_ending_of_str(text), None);
309+ }
310+
311+ #[test]
312+ fn line_end_char_index_rope_slice() {
313+ let r = Rope::from_str("Hello\rworld\nhow\r\nare you?");
314+ let s = &r.slice(..);
315+ #[cfg(not(feature = "unicode-lines"))]
316+ {
317+ assert_eq!(line_end_char_index(s, 0), 11);
318+ assert_eq!(line_end_char_index(s, 1), 15);
319+ assert_eq!(line_end_char_index(s, 2), 25);
320+ }
321+ #[cfg(feature = "unicode-lines")]
322+ {
323+ assert_eq!(line_end_char_index(s, 0), 5);
324+ assert_eq!(line_end_char_index(s, 1), 11);
325+ assert_eq!(line_end_char_index(s, 2), 15);
326+ }
327+ }
328+}
helix-core/src/match_brackets.rsnew file+295
@@ -0,0 +1,295 @@
1+use std::iter;
2+
3+use ropey::RopeSlice;
4+use tree_sitter::Node;
5+
6+use crate::movement::Direction::{self, Backward, Forward};
7+use crate::Syntax;
8+
9+const MAX_PLAINTEXT_SCAN: usize = 10000;
10+const MATCH_LIMIT: usize = 16;
11+
12+// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
13+const PAIRS: &[(char, char)] = &[
14+ ('(', ')'),
15+ ('{', '}'),
16+ ('[', ']'),
17+ ('<', '>'),
18+ ('\'', '\''),
19+ ('\"', '\"'),
20+];
21+
22+/// Returns the position of the matching bracket under cursor.
23+///
24+/// If the cursor is on the opening bracket, the position of
25+/// the closing bracket is returned. If the cursor on the closing
26+/// bracket, the position of the opening bracket is returned.
27+///
28+/// If the cursor is not on a bracket, `None` is returned.
29+///
30+/// If no matching bracket is found, `None` is returned.
31+#[must_use]
32+pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
33+ if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
34+ return None;
35+ }
36+ find_pair(syntax, doc, pos, false)
37+}
38+
39+// Returns the position of the bracket that is closing the current scope.
40+//
41+// If the cursor is on an opening or closing bracket, the function
42+// behaves equivalent to [`find_matching_bracket`].
43+//
44+// If the cursor position is within a scope, the function searches
45+// for the surrounding scope that is surrounded by brackets and
46+// returns the position of the closing bracket for that scope.
47+//
48+// If no surrounding scope is found, the function returns `None`.
49+#[must_use]
50+pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
51+ find_pair(syntax, doc, pos, true)
52+}
53+
54+fn find_pair(
55+ syntax: &Syntax,
56+ doc: RopeSlice,
57+ pos_: usize,
58+ traverse_parents: bool,
59+) -> Option<usize> {
60+ let tree = syntax.tree();
61+ let pos = doc.char_to_byte(pos_);
62+
63+ let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
64+
65+ loop {
66+ if node.is_named() {
67+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
68+ let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
69+
70+ if is_valid_pair(doc, start_char, end_char) {
71+ if end_byte == pos {
72+ return Some(start_char);
73+ }
74+
75+ // We return the end char if the cursor is either on the start char
76+ // or at some arbitrary position between start and end char.
77+ if traverse_parents || start_byte == pos {
78+ return Some(end_char);
79+ }
80+ }
81+ }
82+ // this node itselt wasn't a pair but maybe its siblings are
83+
84+ // check if we are *on* the pair (special cased so we don't look
85+ // at the current node twice and to jump to the start on that case)
86+ if let Some(open) = as_close_pair(doc, &node) {
87+ if let Some(pair_start) = find_pair_end(doc, node.prev_sibling(), open, Backward) {
88+ return Some(pair_start);
89+ }
90+ }
91+
92+ if !traverse_parents {
93+ // check if we are *on* the opening pair (special cased here as
94+ // an opptimization since we only care about bracket on the cursor
95+ // here)
96+ if let Some(close) = as_open_pair(doc, &node) {
97+ if let Some(pair_end) = find_pair_end(doc, node.next_sibling(), close, Forward) {
98+ return Some(pair_end);
99+ }
100+ }
101+ if node.is_named() {
102+ break;
103+ }
104+ }
105+
106+ for close in
107+ iter::successors(node.next_sibling(), |node| node.next_sibling()).take(MATCH_LIMIT)
108+ {
109+ let Some(open) = as_close_pair(doc, &close) else {
110+ continue;
111+ };
112+ if find_pair_end(doc, Some(node), open, Backward).is_some() {
113+ return doc.try_byte_to_char(close.start_byte()).ok();
114+ }
115+ }
116+ let Some(parent) = node.parent() else {
117+ break;
118+ };
119+ node = parent;
120+ }
121+ let node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
122+ if node.child_count() != 0 {
123+ return None;
124+ }
125+ let node_start = doc.byte_to_char(node.start_byte());
126+ find_matching_bracket_plaintext(doc.byte_slice(node.byte_range()), pos_ - node_start)
127+ .map(|pos| pos + node_start)
128+}
129+
130+/// Returns the position of the matching bracket under cursor.
131+/// This function works on plain text and ignores tree-sitter grammar.
132+/// The search is limited to `MAX_PLAINTEXT_SCAN` characters
133+///
134+/// If the cursor is on the opening bracket, the position of
135+/// the closing bracket is returned. If the cursor on the closing
136+/// bracket, the position of the opening bracket is returned.
137+///
138+/// If the cursor is not on a bracket, `None` is returned.
139+///
140+/// If no matching bracket is found, `None` is returned.
141+#[must_use]
142+pub fn find_matching_bracket_plaintext(doc: RopeSlice, cursor_pos: usize) -> Option<usize> {
143+ // Don't do anything when the cursor is not on top of a bracket.
144+ let bracket = doc.char(cursor_pos);
145+ if !is_valid_bracket(bracket) {
146+ return None;
147+ }
148+
149+ // Determine the direction of the matching.
150+ let is_fwd = is_forward_bracket(bracket);
151+ let chars_iter = if is_fwd {
152+ doc.chars_at(cursor_pos + 1)
153+ } else {
154+ doc.chars_at(cursor_pos).reversed()
155+ };
156+
157+ let mut open_cnt = 1;
158+
159+ for (i, candidate) in chars_iter.take(MAX_PLAINTEXT_SCAN).enumerate() {
160+ if candidate == bracket {
161+ open_cnt += 1;
162+ } else if is_valid_pair(
163+ doc,
164+ if is_fwd {
165+ cursor_pos
166+ } else {
167+ cursor_pos - i - 1
168+ },
169+ if is_fwd {
170+ cursor_pos + i + 1
171+ } else {
172+ cursor_pos
173+ },
174+ ) {
175+ // Return when all pending brackets have been closed.
176+ if open_cnt == 1 {
177+ return Some(if is_fwd {
178+ cursor_pos + i + 1
179+ } else {
180+ cursor_pos - i - 1
181+ });
182+ }
183+ open_cnt -= 1;
184+ }
185+ }
186+
187+ None
188+}
189+
190+fn is_valid_bracket(c: char) -> bool {
191+ PAIRS.iter().any(|(l, r)| *l == c || *r == c)
192+}
193+
194+fn is_forward_bracket(c: char) -> bool {
195+ PAIRS.iter().any(|(l, _)| *l == c)
196+}
197+
198+fn is_valid_pair(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
199+ PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
200+}
201+
202+fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
203+ let len = doc.len_bytes();
204+
205+ let start_byte = node.start_byte();
206+ let end_byte = node.end_byte().saturating_sub(1);
207+
208+ if start_byte >= len || end_byte >= len {
209+ return None;
210+ }
211+
212+ Some((start_byte, end_byte))
213+}
214+
215+/// Tests if this node is a pair close char and returns the expected open char
216+fn as_close_pair(doc: RopeSlice, node: &Node) -> Option<char> {
217+ let close = as_char(doc, node)?.1;
218+ PAIRS
219+ .iter()
220+ .find_map(|&(open, close_)| (close_ == close).then_some(open))
221+}
222+
223+/// Checks if `node` or its siblings (at most MATCH_LIMIT nodes) is the specified closing char
224+///
225+/// # Returns
226+///
227+/// The position of the found node or `None` otherwise
228+fn find_pair_end(
229+ doc: RopeSlice,
230+ node: Option<Node>,
231+ end_char: char,
232+ direction: Direction,
233+) -> Option<usize> {
234+ let advance = match direction {
235+ Forward => Node::next_sibling,
236+ Backward => Node::prev_sibling,
237+ };
238+ iter::successors(node, advance)
239+ .take(MATCH_LIMIT)
240+ .find_map(|node| {
241+ let (pos, c) = as_char(doc, &node)?;
242+ (end_char == c).then_some(pos)
243+ })
244+}
245+
246+/// Tests if this node is a pair close char and returns the expected open char
247+fn as_open_pair(doc: RopeSlice, node: &Node) -> Option<char> {
248+ let open = as_char(doc, node)?.1;
249+ PAIRS
250+ .iter()
251+ .find_map(|&(open_, close)| (open_ == open).then_some(close))
252+}
253+
254+/// If node is a single char return it (and its char position)
255+fn as_char(doc: RopeSlice, node: &Node) -> Option<(usize, char)> {
256+ // TODO: multi char/non ASCII pairs
257+ if node.byte_range().len() != 1 {
258+ return None;
259+ }
260+ let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
261+ Some((pos, doc.char(pos)))
262+}
263+
264+#[cfg(test)]
265+mod tests {
266+ use super::*;
267+
268+ #[test]
269+ fn test_find_matching_bracket_current_line_plaintext() {
270+ let assert = |input: &str, pos, expected| {
271+ let input = RopeSlice::from(input);
272+ let actual = find_matching_bracket_plaintext(input, pos);
273+ assert_eq!(expected, actual.unwrap());
274+
275+ let actual = find_matching_bracket_plaintext(input, expected);
276+ assert_eq!(pos, actual.unwrap(), "expected symmetrical behaviour");
277+ };
278+
279+ assert("(hello)", 0, 6);
280+ assert("((hello))", 0, 8);
281+ assert("((hello))", 1, 7);
282+ assert("(((hello)))", 2, 8);
283+
284+ assert("key: ${value}", 6, 12);
285+ assert("key: ${value} # (some comment)", 16, 29);
286+
287+ assert("(paren (paren {bracket}))", 0, 24);
288+ assert("(paren (paren {bracket}))", 7, 23);
289+ assert("(paren (paren {bracket}))", 14, 22);
290+
291+ assert("(prev line\n ) (middle) ( \n next line)", 0, 12);
292+ assert("(prev line\n ) (middle) ( \n next line)", 14, 21);
293+ assert("(prev line\n ) (middle) ( \n next line)", 23, 36);
294+ }
295+}
helix-core/src/movement.rsnew file+1795
@@ -0,0 +1,1795 @@
1+use std::{cmp::Reverse, iter};
2+
3+use ropey::iter::Chars;
4+use tree_sitter::{Node, QueryCursor};
5+
6+use crate::{
7+ char_idx_at_visual_offset,
8+ chars::{categorize_char, char_is_line_ending, CharCategory},
9+ doc_formatter::TextFormat,
10+ graphemes::{
11+ next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
12+ prev_grapheme_boundary,
13+ },
14+ line_ending::rope_is_line_ending,
15+ position::char_idx_at_visual_block_offset,
16+ syntax::LanguageConfiguration,
17+ text_annotations::TextAnnotations,
18+ textobject::TextObject,
19+ visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
20+};
21+
22+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
23+pub enum Direction {
24+ Forward,
25+ Backward,
26+}
27+
28+#[derive(Copy, Clone, PartialEq, Eq)]
29+pub enum Movement {
30+ Extend,
31+ Move,
32+}
33+
34+pub fn move_horizontally(
35+ slice: RopeSlice,
36+ range: Range,
37+ dir: Direction,
38+ count: usize,
39+ behaviour: Movement,
40+ _: &TextFormat,
41+ _: &mut TextAnnotations,
42+) -> Range {
43+ let pos = range.cursor(slice);
44+
45+ // Compute the new position.
46+ let new_pos = match dir {
47+ Direction::Forward => nth_next_grapheme_boundary(slice, pos, count),
48+ Direction::Backward => nth_prev_grapheme_boundary(slice, pos, count),
49+ };
50+
51+ // Compute the final new range.
52+ range.put_cursor(slice, new_pos, behaviour == Movement::Extend)
53+}
54+
55+pub fn move_vertically_visual(
56+ slice: RopeSlice,
57+ range: Range,
58+ dir: Direction,
59+ count: usize,
60+ behaviour: Movement,
61+ text_fmt: &TextFormat,
62+ annotations: &mut TextAnnotations,
63+) -> Range {
64+ if !text_fmt.soft_wrap {
65+ return move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
66+ }
67+ annotations.clear_line_annotations();
68+ let pos = range.cursor(slice);
69+
70+ // Compute the current position's 2d coordinates.
71+ let (visual_pos, block_off) = visual_offset_from_block(slice, pos, pos, text_fmt, annotations);
72+ let new_col = range
73+ .old_visual_position
74+ .map_or(visual_pos.col as u32, |(_, col)| col);
75+
76+ // Compute the new position.
77+ let mut row_off = match dir {
78+ Direction::Forward => count as isize,
79+ Direction::Backward => -(count as isize),
80+ };
81+
82+ // TODO how to handle inline annotations that span an entire visual line (very unlikely).
83+
84+ // Compute visual offset relative to block start to avoid trasversing the block twice
85+ row_off += visual_pos.row as isize;
86+ let new_pos = char_idx_at_visual_offset(
87+ slice,
88+ block_off,
89+ row_off,
90+ new_col as usize,
91+ text_fmt,
92+ annotations,
93+ )
94+ .0;
95+
96+ // Special-case to avoid moving to the end of the last non-empty line.
97+ if behaviour == Movement::Extend && slice.line(slice.char_to_line(new_pos)).len_chars() == 0 {
98+ return range;
99+ }
100+
101+ let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
102+ new_range.old_visual_position = Some((0, new_col));
103+ new_range
104+}
105+
106+pub fn move_vertically(
107+ slice: RopeSlice,
108+ range: Range,
109+ dir: Direction,
110+ count: usize,
111+ behaviour: Movement,
112+ text_fmt: &TextFormat,
113+ annotations: &mut TextAnnotations,
114+) -> Range {
115+ annotations.clear_line_annotations();
116+ let pos = range.cursor(slice);
117+ let line_idx = slice.char_to_line(pos);
118+ let line_start = slice.line_to_char(line_idx);
119+
120+ // Compute the current position's 2d coordinates.
121+ let visual_pos = visual_offset_from_block(slice, line_start, pos, text_fmt, annotations).0;
122+ let (mut new_row, new_col) = range
123+ .old_visual_position
124+ .map_or((visual_pos.row as u32, visual_pos.col as u32), |pos| pos);
125+ new_row = new_row.max(visual_pos.row as u32);
126+ let line_idx = slice.char_to_line(pos);
127+
128+ // Compute the new position.
129+ let mut new_line_idx = match dir {
130+ Direction::Forward => line_idx.saturating_add(count),
131+ Direction::Backward => line_idx.saturating_sub(count),
132+ };
133+
134+ let line = if new_line_idx >= slice.len_lines() - 1 {
135+ // there is no line terminator for the last line
136+ // so the logic below is not necessary here
137+ new_line_idx = slice.len_lines() - 1;
138+ slice
139+ } else {
140+ // char_idx_at_visual_block_offset returns a one-past-the-end index
141+ // in case it reaches the end of the slice
142+ // to avoid moving to the nextline in that case the line terminator is removed from the line
143+ let new_line_end = prev_grapheme_boundary(slice, slice.line_to_char(new_line_idx + 1));
144+ slice.slice(..new_line_end)
145+ };
146+
147+ let new_line_start = line.line_to_char(new_line_idx);
148+
149+ let (new_pos, _) = char_idx_at_visual_block_offset(
150+ line,
151+ new_line_start,
152+ new_row as usize,
153+ new_col as usize,
154+ text_fmt,
155+ annotations,
156+ );
157+
158+ // Special-case to avoid moving to the end of the last non-empty line.
159+ if behaviour == Movement::Extend && slice.line(new_line_idx).len_chars() == 0 {
160+ return range;
161+ }
162+
163+ let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
164+ new_range.old_visual_position = Some((new_row, new_col));
165+ new_range
166+}
167+
168+pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
169+ word_move(slice, range, count, WordMotionTarget::NextWordStart)
170+}
171+
172+pub fn move_next_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
173+ word_move(slice, range, count, WordMotionTarget::NextWordEnd)
174+}
175+
176+pub fn move_prev_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
177+ word_move(slice, range, count, WordMotionTarget::PrevWordStart)
178+}
179+
180+pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
181+ word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
182+}
183+
184+pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
185+ word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
186+}
187+
188+pub fn move_next_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
189+ word_move(slice, range, count, WordMotionTarget::NextLongWordEnd)
190+}
191+
192+pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
193+ word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
194+}
195+
196+pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
197+ word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
198+}
199+
200+fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
201+ let is_prev = matches!(
202+ target,
203+ WordMotionTarget::PrevWordStart
204+ | WordMotionTarget::PrevLongWordStart
205+ | WordMotionTarget::PrevWordEnd
206+ | WordMotionTarget::PrevLongWordEnd
207+ );
208+
209+ // Special-case early-out.
210+ if (is_prev && range.head == 0) || (!is_prev && range.head == slice.len_chars()) {
211+ return range;
212+ }
213+
214+ // Prepare the range appropriately based on the target movement
215+ // direction. This is addressing two things at once:
216+ //
217+ // 1. Block-cursor semantics.
218+ // 2. The anchor position being irrelevant to the output result.
219+ #[allow(clippy::collapsible_else_if)] // Makes the structure clearer in this case.
220+ let start_range = if is_prev {
221+ if range.anchor < range.head {
222+ Range::new(range.head, prev_grapheme_boundary(slice, range.head))
223+ } else {
224+ Range::new(next_grapheme_boundary(slice, range.head), range.head)
225+ }
226+ } else {
227+ if range.anchor < range.head {
228+ Range::new(prev_grapheme_boundary(slice, range.head), range.head)
229+ } else {
230+ Range::new(range.head, next_grapheme_boundary(slice, range.head))
231+ }
232+ };
233+
234+ // Do the main work.
235+ let mut range = start_range;
236+ for _ in 0..count {
237+ let next_range = slice.chars_at(range.head).range_to_target(target, range);
238+ if range == next_range {
239+ break;
240+ }
241+ range = next_range;
242+ }
243+ range
244+}
245+
246+pub fn move_prev_paragraph(
247+ slice: RopeSlice,
248+ range: Range,
249+ count: usize,
250+ behavior: Movement,
251+) -> Range {
252+ let mut line = range.cursor_line(slice);
253+ let first_char = slice.line_to_char(line) == range.cursor(slice);
254+ let prev_line_empty = rope_is_line_ending(slice.line(line.saturating_sub(1)));
255+ let curr_line_empty = rope_is_line_ending(slice.line(line));
256+ let prev_empty_to_line = prev_line_empty && !curr_line_empty;
257+
258+ // skip character before paragraph boundary
259+ if prev_empty_to_line && !first_char {
260+ line += 1;
261+ }
262+ let mut lines = slice.lines_at(line);
263+ lines.reverse();
264+ let mut lines = lines.map(rope_is_line_ending).peekable();
265+ let mut last_line = line;
266+ for _ in 0..count {
267+ while lines.next_if(|&e| e).is_some() {
268+ line -= 1;
269+ }
270+ while lines.next_if(|&e| !e).is_some() {
271+ line -= 1;
272+ }
273+ if line == last_line {
274+ break;
275+ }
276+ last_line = line;
277+ }
278+
279+ let head = slice.line_to_char(line);
280+ let anchor = if behavior == Movement::Move {
281+ // exclude first character after paragraph boundary
282+ if prev_empty_to_line && first_char {
283+ range.cursor(slice)
284+ } else {
285+ range.head
286+ }
287+ } else {
288+ range.put_cursor(slice, head, true).anchor
289+ };
290+ Range::new(anchor, head)
291+}
292+
293+pub fn move_next_paragraph(
294+ slice: RopeSlice,
295+ range: Range,
296+ count: usize,
297+ behavior: Movement,
298+) -> Range {
299+ let mut line = range.cursor_line(slice);
300+ let last_char =
301+ prev_grapheme_boundary(slice, slice.line_to_char(line + 1)) == range.cursor(slice);
302+ let curr_line_empty = rope_is_line_ending(slice.line(line));
303+ let next_line_empty =
304+ rope_is_line_ending(slice.line(slice.len_lines().saturating_sub(1).min(line + 1)));
305+ let curr_empty_to_line = curr_line_empty && !next_line_empty;
306+
307+ // skip character after paragraph boundary
308+ if curr_empty_to_line && last_char {
309+ line += 1;
310+ }
311+ let mut lines = slice.lines_at(line).map(rope_is_line_ending).peekable();
312+ let mut last_line = line;
313+ for _ in 0..count {
314+ while lines.next_if(|&e| !e).is_some() {
315+ line += 1;
316+ }
317+ while lines.next_if(|&e| e).is_some() {
318+ line += 1;
319+ }
320+ if line == last_line {
321+ break;
322+ }
323+ last_line = line;
324+ }
325+ let head = slice.line_to_char(line);
326+ let anchor = if behavior == Movement::Move {
327+ if curr_empty_to_line && last_char {
328+ range.head
329+ } else {
330+ range.cursor(slice)
331+ }
332+ } else {
333+ range.put_cursor(slice, head, true).anchor
334+ };
335+ Range::new(anchor, head)
336+}
337+
338+// ---- util ------------
339+
340+#[inline]
341+/// Returns first index that doesn't satisfy a given predicate when
342+/// advancing the character index.
343+///
344+/// Returns none if all characters satisfy the predicate.
345+pub fn skip_while<F>(slice: RopeSlice, pos: usize, fun: F) -> Option<usize>
346+where
347+ F: Fn(char) -> bool,
348+{
349+ let mut chars = slice.chars_at(pos).enumerate();
350+ chars.find_map(|(i, c)| if !fun(c) { Some(pos + i) } else { None })
351+}
352+
353+#[inline]
354+/// Returns first index that doesn't satisfy a given predicate when
355+/// retreating the character index, saturating if all elements satisfy
356+/// the condition.
357+pub fn backwards_skip_while<F>(slice: RopeSlice, pos: usize, fun: F) -> Option<usize>
358+where
359+ F: Fn(char) -> bool,
360+{
361+ let mut chars_starting_from_next = slice.chars_at(pos);
362+ let mut backwards = iter::from_fn(|| chars_starting_from_next.prev()).enumerate();
363+ backwards.find_map(|(i, c)| {
364+ if !fun(c) {
365+ Some(pos.saturating_sub(i))
366+ } else {
367+ None
368+ }
369+ })
370+}
371+
372+/// Possible targets of a word motion
373+#[derive(Copy, Clone, Debug)]
374+pub enum WordMotionTarget {
375+ NextWordStart,
376+ NextWordEnd,
377+ PrevWordStart,
378+ PrevWordEnd,
379+ // A "Long word" (also known as a WORD in Vim/Kakoune) is strictly
380+ // delimited by whitespace, and can consist of punctuation as well
381+ // as alphanumerics.
382+ NextLongWordStart,
383+ NextLongWordEnd,
384+ PrevLongWordStart,
385+ PrevLongWordEnd,
386+}
387+
388+pub trait CharHelpers {
389+ fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range;
390+}
391+
392+impl CharHelpers for Chars<'_> {
393+ /// Note: this only changes the anchor of the range if the head is effectively
394+ /// starting on a boundary (either directly or after skipping newline characters).
395+ /// Any other changes to the anchor should be handled by the calling code.
396+ fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range {
397+ let is_prev = matches!(
398+ target,
399+ WordMotionTarget::PrevWordStart
400+ | WordMotionTarget::PrevLongWordStart
401+ | WordMotionTarget::PrevWordEnd
402+ | WordMotionTarget::PrevLongWordEnd
403+ );
404+
405+ // Reverse the iterator if needed for the motion direction.
406+ if is_prev {
407+ self.reverse();
408+ }
409+
410+ // Function to advance index in the appropriate motion direction.
411+ let advance: &dyn Fn(&mut usize) = if is_prev {
412+ &|idx| *idx = idx.saturating_sub(1)
413+ } else {
414+ &|idx| *idx += 1
415+ };
416+
417+ // Initialize state variables.
418+ let mut anchor = origin.anchor;
419+ let mut head = origin.head;
420+ let mut prev_ch = {
421+ let ch = self.prev();
422+ if ch.is_some() {
423+ self.next();
424+ }
425+ ch
426+ };
427+
428+ // Skip any initial newline characters.
429+ while let Some(ch) = self.next() {
430+ if char_is_line_ending(ch) {
431+ prev_ch = Some(ch);
432+ advance(&mut head);
433+ } else {
434+ self.prev();
435+ break;
436+ }
437+ }
438+ if prev_ch.map(char_is_line_ending).unwrap_or(false) {
439+ anchor = head;
440+ }
441+
442+ // Find our target position(s).
443+ let head_start = head;
444+ #[allow(clippy::while_let_on_iterator)] // Clippy's suggestion to fix doesn't work here.
445+ while let Some(next_ch) = self.next() {
446+ if prev_ch.is_none() || reached_target(target, prev_ch.unwrap(), next_ch) {
447+ if head == head_start {
448+ anchor = head;
449+ } else {
450+ break;
451+ }
452+ }
453+ prev_ch = Some(next_ch);
454+ advance(&mut head);
455+ }
456+
457+ // Un-reverse the iterator if needed.
458+ if is_prev {
459+ self.reverse();
460+ }
461+
462+ Range::new(anchor, head)
463+ }
464+}
465+
466+fn is_word_boundary(a: char, b: char) -> bool {
467+ categorize_char(a) != categorize_char(b)
468+}
469+
470+fn is_long_word_boundary(a: char, b: char) -> bool {
471+ match (categorize_char(a), categorize_char(b)) {
472+ (CharCategory::Word, CharCategory::Punctuation)
473+ | (CharCategory::Punctuation, CharCategory::Word) => false,
474+ (a, b) if a != b => true,
475+ _ => false,
476+ }
477+}
478+
479+fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> bool {
480+ match target {
481+ WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => {
482+ is_word_boundary(prev_ch, next_ch)
483+ && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
484+ }
485+ WordMotionTarget::NextWordEnd | WordMotionTarget::PrevWordStart => {
486+ is_word_boundary(prev_ch, next_ch)
487+ && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
488+ }
489+ WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
490+ is_long_word_boundary(prev_ch, next_ch)
491+ && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
492+ }
493+ WordMotionTarget::NextLongWordEnd | WordMotionTarget::PrevLongWordStart => {
494+ is_long_word_boundary(prev_ch, next_ch)
495+ && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
496+ }
497+ }
498+}
499+
500+/// Finds the range of the next or previous textobject in the syntax sub-tree of `node`.
501+/// Returns the range in the forwards direction.
502+pub fn goto_treesitter_object(
503+ slice: RopeSlice,
504+ range: Range,
505+ object_name: &str,
506+ dir: Direction,
507+ slice_tree: Node,
508+ lang_config: &LanguageConfiguration,
509+ count: usize,
510+) -> Range {
511+ let get_range = move |range: Range| -> Option<Range> {
512+ let byte_pos = slice.char_to_byte(range.cursor(slice));
513+
514+ let cap_name = |t: TextObject| format!("{}.{}", object_name, t);
515+ let mut cursor = QueryCursor::new();
516+ let nodes = lang_config.textobject_query()?.capture_nodes_any(
517+ &[
518+ &cap_name(TextObject::Movement),
519+ &cap_name(TextObject::Around),
520+ &cap_name(TextObject::Inside),
521+ ],
522+ slice_tree,
523+ slice,
524+ &mut cursor,
525+ )?;
526+
527+ let node = match dir {
528+ Direction::Forward => nodes
529+ .filter(|n| n.start_byte() > byte_pos)
530+ .min_by_key(|n| (n.start_byte(), Reverse(n.end_byte())))?,
531+ Direction::Backward => nodes
532+ .filter(|n| n.end_byte() < byte_pos)
533+ .max_by_key(|n| (n.end_byte(), Reverse(n.start_byte())))?,
534+ };
535+
536+ let len = slice.len_bytes();
537+ let start_byte = node.start_byte();
538+ let end_byte = node.end_byte();
539+ if start_byte >= len || end_byte >= len {
540+ return None;
541+ }
542+
543+ let start_char = slice.byte_to_char(start_byte);
544+ let end_char = slice.byte_to_char(end_byte);
545+
546+ // head of range should be at beginning
547+ Some(Range::new(start_char, end_char))
548+ };
549+ let mut last_range = range;
550+ for _ in 0..count {
551+ match get_range(last_range) {
552+ Some(r) if r != last_range => last_range = r,
553+ _ => break,
554+ }
555+ }
556+ last_range
557+}
558+
559+fn find_parent_start(mut node: Node) -> Option<Node> {
560+ let start = node.start_byte();
561+
562+ while node.start_byte() >= start || !node.is_named() {
563+ node = node.parent()?;
564+ }
565+
566+ Some(node)
567+}
568+
569+pub fn move_parent_node_end(
570+ syntax: &Syntax,
571+ text: RopeSlice,
572+ selection: Selection,
573+ dir: Direction,
574+ movement: Movement,
575+) -> Selection {
576+ let tree = syntax.tree();
577+
578+ selection.transform(|range| {
579+ let start_from = text.char_to_byte(range.from());
580+ let start_to = text.char_to_byte(range.to());
581+
582+ let mut node = match tree
583+ .root_node()
584+ .named_descendant_for_byte_range(start_from, start_to)
585+ {
586+ Some(node) => node,
587+ None => {
588+ log::debug!(
589+ "no descendant found for byte range: {} - {}",
590+ start_from,
591+ start_to
592+ );
593+ return range;
594+ }
595+ };
596+
597+ let mut end_head = match dir {
598+ // moving forward, we always want to move one past the end of the
599+ // current node, so use the end byte of the current node, which is an exclusive
600+ // end of the range
601+ Direction::Forward => text.byte_to_char(node.end_byte()),
602+
603+ // moving backward, we want the cursor to land on the start char of
604+ // the current node, or if it is already at the start of a node, to traverse up to
605+ // the parent
606+ Direction::Backward => {
607+ let end_head = text.byte_to_char(node.start_byte());
608+
609+ // if we're already on the beginning, look up to the parent
610+ if end_head == range.cursor(text) {
611+ node = find_parent_start(node).unwrap_or(node);
612+ text.byte_to_char(node.start_byte())
613+ } else {
614+ end_head
615+ }
616+ }
617+ };
618+
619+ if movement == Movement::Move {
620+ // preserve direction of original range
621+ if range.direction() == Direction::Forward {
622+ Range::new(end_head, end_head + 1)
623+ } else {
624+ Range::new(end_head + 1, end_head)
625+ }
626+ } else {
627+ // if we end up with a forward range, then adjust it to be one past
628+ // where we want
629+ if end_head >= range.anchor {
630+ end_head += 1;
631+ }
632+
633+ Range::new(range.anchor, end_head)
634+ }
635+ })
636+}
637+
638+#[cfg(test)]
639+mod test {
640+ use ropey::Rope;
641+
642+ use crate::{coords_at_pos, pos_at_coords};
643+
644+ use super::*;
645+
646+ const SINGLE_LINE_SAMPLE: &str = "This is a simple alphabetic line";
647+ const MULTILINE_SAMPLE: &str = "\
648+ Multiline\n\
649+ text sample\n\
650+ which\n\
651+ is merely alphabetic\n\
652+ and whitespaced\n\
653+ ";
654+
655+ const MULTIBYTE_CHARACTER_SAMPLE: &str = "\
656+ パーティーへ行かないか\n\
657+ The text above is Japanese\n\
658+ ";
659+
660+ #[test]
661+ fn test_vertical_move() {
662+ let text = Rope::from("abcd\nefg\nwrs");
663+ let slice = text.slice(..);
664+ let pos = pos_at_coords(slice, (0, 4).into(), true);
665+
666+ let range = Range::new(pos, pos);
667+ assert_eq!(
668+ coords_at_pos(
669+ slice,
670+ move_vertically_visual(
671+ slice,
672+ range,
673+ Direction::Forward,
674+ 1,
675+ Movement::Move,
676+ &TextFormat::default(),
677+ &mut TextAnnotations::default(),
678+ )
679+ .head
680+ ),
681+ (1, 3).into()
682+ );
683+ }
684+
685+ #[test]
686+ fn horizontal_moves_through_single_line_text() {
687+ let text = Rope::from(SINGLE_LINE_SAMPLE);
688+ let slice = text.slice(..);
689+ let position = pos_at_coords(slice, (0, 0).into(), true);
690+
691+ let mut range = Range::point(position);
692+
693+ let moves_and_expected_coordinates = [
694+ ((Direction::Forward, 1usize), (0, 1)), // T|his is a simple alphabetic line
695+ ((Direction::Forward, 2usize), (0, 3)), // Thi|s is a simple alphabetic line
696+ ((Direction::Forward, 0usize), (0, 3)), // Thi|s is a simple alphabetic line
697+ ((Direction::Forward, 999usize), (0, 32)), // This is a simple alphabetic line|
698+ ((Direction::Forward, 999usize), (0, 32)), // This is a simple alphabetic line|
699+ ((Direction::Backward, 999usize), (0, 0)), // |This is a simple alphabetic line
700+ ];
701+
702+ for ((direction, amount), coordinates) in moves_and_expected_coordinates {
703+ range = move_horizontally(
704+ slice,
705+ range,
706+ direction,
707+ amount,
708+ Movement::Move,
709+ &TextFormat::default(),
710+ &mut TextAnnotations::default(),
711+ );
712+ assert_eq!(coords_at_pos(slice, range.head), coordinates.into())
713+ }
714+ }
715+
716+ #[test]
717+ fn horizontal_moves_through_multiline_text() {
718+ let text = Rope::from(MULTILINE_SAMPLE);
719+ let slice = text.slice(..);
720+ let position = pos_at_coords(slice, (0, 0).into(), true);
721+
722+ let mut range = Range::point(position);
723+
724+ let moves_and_expected_coordinates = [
725+ ((Direction::Forward, 11usize), (1, 1)), // Multiline\nt|ext sample\n...
726+ ((Direction::Backward, 1usize), (1, 0)), // Multiline\n|text sample\n...
727+ ((Direction::Backward, 5usize), (0, 5)), // Multi|line\ntext sample\n...
728+ ((Direction::Backward, 999usize), (0, 0)), // |Multiline\ntext sample\n...
729+ ((Direction::Forward, 3usize), (0, 3)), // Mul|tiline\ntext sample\n...
730+ ((Direction::Forward, 0usize), (0, 3)), // Mul|tiline\ntext sample\n...
731+ ((Direction::Backward, 0usize), (0, 3)), // Mul|tiline\ntext sample\n...
732+ ((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
733+ ((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
734+ ];
735+
736+ for ((direction, amount), coordinates) in moves_and_expected_coordinates {
737+ range = move_horizontally(
738+ slice,
739+ range,
740+ direction,
741+ amount,
742+ Movement::Move,
743+ &TextFormat::default(),
744+ &mut TextAnnotations::default(),
745+ );
746+ assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
747+ assert_eq!(range.head, range.anchor);
748+ }
749+ }
750+
751+ #[test]
752+ fn selection_extending_moves_in_single_line_text() {
753+ let text = Rope::from(SINGLE_LINE_SAMPLE);
754+ let slice = text.slice(..);
755+ let position = pos_at_coords(slice, (0, 0).into(), true);
756+
757+ let mut range = Range::point(position);
758+ let original_anchor = range.anchor;
759+
760+ let moves = [
761+ (Direction::Forward, 1usize),
762+ (Direction::Forward, 5usize),
763+ (Direction::Backward, 3usize),
764+ ];
765+
766+ for (direction, amount) in moves {
767+ range = move_horizontally(
768+ slice,
769+ range,
770+ direction,
771+ amount,
772+ Movement::Extend,
773+ &TextFormat::default(),
774+ &mut TextAnnotations::default(),
775+ );
776+ assert_eq!(range.anchor, original_anchor);
777+ }
778+ }
779+
780+ #[test]
781+ fn vertical_moves_in_single_column() {
782+ let text = Rope::from(MULTILINE_SAMPLE);
783+ let slice = text.slice(..);
784+ let position = pos_at_coords(slice, (0, 0).into(), true);
785+ let mut range = Range::point(position);
786+ let moves_and_expected_coordinates = [
787+ ((Direction::Forward, 1usize), (1, 0)),
788+ ((Direction::Forward, 2usize), (3, 0)),
789+ ((Direction::Forward, 1usize), (4, 0)),
790+ ((Direction::Backward, 999usize), (0, 0)),
791+ ((Direction::Forward, 4usize), (4, 0)),
792+ ((Direction::Forward, 0usize), (4, 0)),
793+ ((Direction::Backward, 0usize), (4, 0)),
794+ ((Direction::Forward, 5), (5, 0)),
795+ ((Direction::Forward, 999usize), (5, 0)),
796+ ];
797+
798+ for ((direction, amount), coordinates) in moves_and_expected_coordinates {
799+ range = move_vertically_visual(
800+ slice,
801+ range,
802+ direction,
803+ amount,
804+ Movement::Move,
805+ &TextFormat::default(),
806+ &mut TextAnnotations::default(),
807+ );
808+ assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
809+ assert_eq!(range.head, range.anchor);
810+ }
811+ }
812+
813+ #[test]
814+ fn vertical_moves_jumping_column() {
815+ let text = Rope::from(MULTILINE_SAMPLE);
816+ let slice = text.slice(..);
817+ let position = pos_at_coords(slice, (0, 0).into(), true);
818+ let mut range = Range::point(position);
819+
820+ enum Axis {
821+ H,
822+ V,
823+ }
824+ let moves_and_expected_coordinates = [
825+ // Places cursor at the end of line
826+ ((Axis::H, Direction::Forward, 8usize), (0, 8)),
827+ // First descent preserves column as the target line is wider
828+ ((Axis::V, Direction::Forward, 1usize), (1, 8)),
829+ // Second descent clamps column as the target line is shorter
830+ ((Axis::V, Direction::Forward, 1usize), (2, 5)),
831+ // Third descent restores the original column
832+ ((Axis::V, Direction::Forward, 1usize), (3, 8)),
833+ // Behaviour is preserved even through long jumps
834+ ((Axis::V, Direction::Backward, 999usize), (0, 8)),
835+ ((Axis::V, Direction::Forward, 4usize), (4, 8)),
836+ ((Axis::V, Direction::Forward, 999usize), (5, 0)),
837+ ];
838+
839+ for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
840+ range = match axis {
841+ Axis::H => move_horizontally(
842+ slice,
843+ range,
844+ direction,
845+ amount,
846+ Movement::Move,
847+ &TextFormat::default(),
848+ &mut TextAnnotations::default(),
849+ ),
850+ Axis::V => move_vertically_visual(
851+ slice,
852+ range,
853+ direction,
854+ amount,
855+ Movement::Move,
856+ &TextFormat::default(),
857+ &mut TextAnnotations::default(),
858+ ),
859+ };
860+ assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
861+ assert_eq!(range.head, range.anchor);
862+ }
863+ }
864+
865+ #[test]
866+ fn multibyte_character_wide_column_jumps() {
867+ let text = Rope::from(MULTIBYTE_CHARACTER_SAMPLE);
868+ let slice = text.slice(..);
869+ let position = pos_at_coords(slice, (0, 0).into(), true);
870+ let mut range = Range::point(position);
871+
872+ // FIXME: The behaviour captured in this test diverges from both Kakoune and Vim. These
873+ // will attempt to preserve the horizontal position of the cursor, rather than
874+ // placing it at the same character index.
875+ enum Axis {
876+ H,
877+ V,
878+ }
879+ let moves_and_expected_coordinates = [
880+ // Places cursor at the fourth kana.
881+ ((Axis::H, Direction::Forward, 4), (0, 4)),
882+ // Descent places cursor at the 8th character.
883+ ((Axis::V, Direction::Forward, 1usize), (1, 8)),
884+ // Moving back 2 characters.
885+ ((Axis::H, Direction::Backward, 2usize), (1, 6)),
886+ // Jumping back up 1 line.
887+ ((Axis::V, Direction::Backward, 1usize), (0, 3)),
888+ ];
889+
890+ for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
891+ range = match axis {
892+ Axis::H => move_horizontally(
893+ slice,
894+ range,
895+ direction,
896+ amount,
897+ Movement::Move,
898+ &TextFormat::default(),
899+ &mut TextAnnotations::default(),
900+ ),
901+ Axis::V => move_vertically_visual(
902+ slice,
903+ range,
904+ direction,
905+ amount,
906+ Movement::Move,
907+ &TextFormat::default(),
908+ &mut TextAnnotations::default(),
909+ ),
910+ };
911+ assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
912+ assert_eq!(range.head, range.anchor);
913+ }
914+ }
915+
916+ #[test]
917+ #[should_panic]
918+ fn nonsensical_ranges_panic_on_forward_movement_attempt_in_debug_mode() {
919+ move_next_word_start(Rope::from("Sample").slice(..), Range::point(99999999), 1);
920+ }
921+
922+ #[test]
923+ #[should_panic]
924+ fn nonsensical_ranges_panic_on_forward_to_end_movement_attempt_in_debug_mode() {
925+ move_next_word_end(Rope::from("Sample").slice(..), Range::point(99999999), 1);
926+ }
927+
928+ #[test]
929+ #[should_panic]
930+ fn nonsensical_ranges_panic_on_backwards_movement_attempt_in_debug_mode() {
931+ move_prev_word_start(Rope::from("Sample").slice(..), Range::point(99999999), 1);
932+ }
933+
934+ #[test]
935+ fn test_behaviour_when_moving_to_start_of_next_words() {
936+ let tests = [
937+ ("Basic forward motion stops at the first space",
938+ vec![(1, Range::new(0, 0), Range::new(0, 6))]),
939+ (" Starting from a boundary advances the anchor",
940+ vec![(1, Range::new(0, 0), Range::new(1, 10))]),
941+ ("Long whitespace gap is bridged by the head",
942+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
943+ ("Previous anchor is irrelevant for forward motions",
944+ vec![(1, Range::new(12, 0), Range::new(0, 9))]),
945+ (" Starting from whitespace moves to last space in sequence",
946+ vec![(1, Range::new(0, 0), Range::new(0, 4))]),
947+ ("Starting from mid-word leaves anchor at start position and moves head",
948+ vec![(1, Range::new(3, 3), Range::new(3, 9))]),
949+ ("Identifiers_with_underscores are considered a single word",
950+ vec![(1, Range::new(0, 0), Range::new(0, 29))]),
951+ ("Jumping\n into starting whitespace selects the spaces before 'into'",
952+ vec![(1, Range::new(0, 7), Range::new(8, 12))]),
953+ ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
954+ vec![
955+ (1, Range::new(0, 0), Range::new(0, 12)),
956+ (1, Range::new(0, 12), Range::new(12, 15)),
957+ (1, Range::new(12, 15), Range::new(15, 18))
958+ ]),
959+ ("... ... punctuation and spaces behave as expected",
960+ vec![
961+ (1, Range::new(0, 0), Range::new(0, 6)),
962+ (1, Range::new(0, 6), Range::new(6, 10)),
963+ ]),
964+ (".._.._ punctuation is not joined by underscores into a single block",
965+ vec![(1, Range::new(0, 0), Range::new(0, 2))]),
966+ ("Newlines\n\nare bridged seamlessly.",
967+ vec![
968+ (1, Range::new(0, 0), Range::new(0, 8)),
969+ (1, Range::new(0, 8), Range::new(10, 14)),
970+ ]),
971+ ("Jumping\n\n\n\n\n\n from newlines to whitespace selects whitespace.",
972+ vec![
973+ (1, Range::new(0, 9), Range::new(13, 16)),
974+ ]),
975+ ("A failed motion does not modify the range",
976+ vec![
977+ (3, Range::new(37, 41), Range::new(37, 41)),
978+ ]),
979+ ("oh oh oh two character words!",
980+ vec![
981+ (1, Range::new(0, 0), Range::new(0, 3)),
982+ (1, Range::new(0, 3), Range::new(3, 6)),
983+ (1, Range::new(0, 2), Range::new(1, 3)),
984+ ]),
985+ ("Multiple motions at once resolve correctly",
986+ vec![
987+ (3, Range::new(0, 0), Range::new(17, 20)),
988+ ]),
989+ ("Excessive motions are performed partially",
990+ vec![
991+ (999, Range::new(0, 0), Range::new(32, 41)),
992+ ]),
993+ ("", // Edge case of moving forward in empty string
994+ vec![
995+ (1, Range::new(0, 0), Range::new(0, 0)),
996+ ]),
997+ ("\n\n\n\n\n", // Edge case of moving forward in all newlines
998+ vec![
999+ (1, Range::new(0, 0), Range::new(5, 5)),
1000+ ]),
1001+ ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
1002+ vec![
1003+ (1, Range::new(0, 0), Range::new(1, 4)),
1004+ (1, Range::new(1, 4), Range::new(5, 8)),
1005+ ]),
1006+ ("ヒーリクス multibyte characters behave as normal characters",
1007+ vec![
1008+ (1, Range::new(0, 0), Range::new(0, 6)),
1009+ ]),
1010+ ];
1011+
1012+ for (sample, scenario) in tests {
1013+ for (count, begin, expected_end) in scenario.into_iter() {
1014+ let range = move_next_word_start(Rope::from(sample).slice(..), begin, count);
1015+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1016+ }
1017+ }
1018+ }
1019+
1020+ #[test]
1021+ fn test_behaviour_when_moving_to_start_of_next_long_words() {
1022+ let tests = [
1023+ ("Basic forward motion stops at the first space",
1024+ vec![(1, Range::new(0, 0), Range::new(0, 6))]),
1025+ (" Starting from a boundary advances the anchor",
1026+ vec![(1, Range::new(0, 0), Range::new(1, 10))]),
1027+ ("Long whitespace gap is bridged by the head",
1028+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
1029+ ("Previous anchor is irrelevant for forward motions",
1030+ vec![(1, Range::new(12, 0), Range::new(0, 9))]),
1031+ (" Starting from whitespace moves to last space in sequence",
1032+ vec![(1, Range::new(0, 0), Range::new(0, 4))]),
1033+ ("Starting from mid-word leaves anchor at start position and moves head",
1034+ vec![(1, Range::new(3, 3), Range::new(3, 9))]),
1035+ ("Identifiers_with_underscores are considered a single word",
1036+ vec![(1, Range::new(0, 0), Range::new(0, 29))]),
1037+ ("Jumping\n into starting whitespace selects the spaces before 'into'",
1038+ vec![(1, Range::new(0, 7), Range::new(8, 12))]),
1039+ ("alphanumeric.!,and.?=punctuation are not treated any differently than alphanumerics",
1040+ vec![
1041+ (1, Range::new(0, 0), Range::new(0, 33)),
1042+ ]),
1043+ ("... ... punctuation and spaces behave as expected",
1044+ vec![
1045+ (1, Range::new(0, 0), Range::new(0, 6)),
1046+ (1, Range::new(0, 6), Range::new(6, 10)),
1047+ ]),
1048+ (".._.._ punctuation is joined by underscores into a single word, as it behaves like alphanumerics",
1049+ vec![(1, Range::new(0, 0), Range::new(0, 7))]),
1050+ ("Newlines\n\nare bridged seamlessly.",
1051+ vec![
1052+ (1, Range::new(0, 0), Range::new(0, 8)),
1053+ (1, Range::new(0, 8), Range::new(10, 14)),
1054+ ]),
1055+ ("Jumping\n\n\n\n\n\n from newlines to whitespace selects whitespace.",
1056+ vec![
1057+ (1, Range::new(0, 9), Range::new(13, 16)),
1058+ ]),
1059+ ("A failed motion does not modify the range",
1060+ vec![
1061+ (3, Range::new(37, 41), Range::new(37, 41)),
1062+ ]),
1063+ ("oh oh oh two character words!",
1064+ vec![
1065+ (1, Range::new(0, 0), Range::new(0, 3)),
1066+ (1, Range::new(0, 3), Range::new(3, 6)),
1067+ (1, Range::new(0, 1), Range::new(0, 3)),
1068+ ]),
1069+ ("Multiple motions at once resolve correctly",
1070+ vec![
1071+ (3, Range::new(0, 0), Range::new(17, 20)),
1072+ ]),
1073+ ("Excessive motions are performed partially",
1074+ vec![
1075+ (999, Range::new(0, 0), Range::new(32, 41)),
1076+ ]),
1077+ ("", // Edge case of moving forward in empty string
1078+ vec![
1079+ (1, Range::new(0, 0), Range::new(0, 0)),
1080+ ]),
1081+ ("\n\n\n\n\n", // Edge case of moving forward in all newlines
1082+ vec![
1083+ (1, Range::new(0, 0), Range::new(5, 5)),
1084+ ]),
1085+ ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
1086+ vec![
1087+ (1, Range::new(0, 0), Range::new(1, 4)),
1088+ (1, Range::new(1, 4), Range::new(5, 8)),
1089+ ]),
1090+ ("ヒー..リクス multibyte characters behave as normal characters, including their interaction with punctuation",
1091+ vec![
1092+ (1, Range::new(0, 0), Range::new(0, 8)),
1093+ ]),
1094+ ];
1095+
1096+ for (sample, scenario) in tests {
1097+ for (count, begin, expected_end) in scenario.into_iter() {
1098+ let range = move_next_long_word_start(Rope::from(sample).slice(..), begin, count);
1099+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1100+ }
1101+ }
1102+ }
1103+
1104+ #[test]
1105+ fn test_behaviour_when_moving_to_start_of_previous_words() {
1106+ let tests = [
1107+ ("Basic backward motion from the middle of a word",
1108+ vec![(1, Range::new(3, 3), Range::new(4, 0))]),
1109+
1110+ // // Why do we want this behavior? The current behavior fails this
1111+ // // test, but seems better and more consistent.
1112+ // ("Starting from after boundary retreats the anchor",
1113+ // vec![(1, Range::new(0, 9), Range::new(8, 0))]),
1114+
1115+ (" Jump to start of a word preceded by whitespace",
1116+ vec![(1, Range::new(5, 5), Range::new(6, 4))]),
1117+ (" Jump to start of line from start of word preceded by whitespace",
1118+ vec![(1, Range::new(4, 4), Range::new(4, 0))]),
1119+ ("Previous anchor is irrelevant for backward motions",
1120+ vec![(1, Range::new(12, 5), Range::new(6, 0))]),
1121+ (" Starting from whitespace moves to first space in sequence",
1122+ vec![(1, Range::new(0, 4), Range::new(4, 0))]),
1123+ ("Identifiers_with_underscores are considered a single word",
1124+ vec![(1, Range::new(0, 20), Range::new(20, 0))]),
1125+ ("Jumping\n \nback through a newline selects whitespace",
1126+ vec![(1, Range::new(0, 13), Range::new(12, 8))]),
1127+ ("Jumping to start of word from the end selects the word",
1128+ vec![(1, Range::new(6, 7), Range::new(7, 0))]),
1129+ ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
1130+ vec![
1131+ (1, Range::new(29, 30), Range::new(30, 21)),
1132+ (1, Range::new(30, 21), Range::new(21, 18)),
1133+ (1, Range::new(21, 18), Range::new(18, 15))
1134+ ]),
1135+ ("... ... punctuation and spaces behave as expected",
1136+ vec![
1137+ (1, Range::new(0, 10), Range::new(10, 6)),
1138+ (1, Range::new(10, 6), Range::new(6, 0)),
1139+ ]),
1140+ (".._.._ punctuation is not joined by underscores into a single block",
1141+ vec![(1, Range::new(0, 6), Range::new(5, 3))]),
1142+ ("Newlines\n\nare bridged seamlessly.",
1143+ vec![
1144+ (1, Range::new(0, 10), Range::new(8, 0)),
1145+ ]),
1146+ ("Jumping \n\n\n\n\nback from within a newline group selects previous block",
1147+ vec![
1148+ (1, Range::new(0, 13), Range::new(11, 0)),
1149+ ]),
1150+ ("Failed motions do not modify the range",
1151+ vec![
1152+ (0, Range::new(3, 0), Range::new(3, 0)),
1153+ ]),
1154+ ("Multiple motions at once resolve correctly",
1155+ vec![
1156+ (3, Range::new(18, 18), Range::new(9, 0)),
1157+ ]),
1158+ ("Excessive motions are performed partially",
1159+ vec![
1160+ (999, Range::new(40, 40), Range::new(10, 0)),
1161+ ]),
1162+ ("", // Edge case of moving backwards in empty string
1163+ vec![
1164+ (1, Range::new(0, 0), Range::new(0, 0)),
1165+ ]),
1166+ ("\n\n\n\n\n", // Edge case of moving backwards in all newlines
1167+ vec![
1168+ (1, Range::new(5, 5), Range::new(0, 0)),
1169+ ]),
1170+ (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1171+ vec![
1172+ (1, Range::new(0, 8), Range::new(7, 4)),
1173+ (1, Range::new(7, 4), Range::new(3, 0)),
1174+ ]),
1175+ ("ヒーリクス multibyte characters behave as normal characters",
1176+ vec![
1177+ (1, Range::new(0, 6), Range::new(6, 0)),
1178+ ]),
1179+ ];
1180+
1181+ for (sample, scenario) in tests {
1182+ for (count, begin, expected_end) in scenario.into_iter() {
1183+ let range = move_prev_word_start(Rope::from(sample).slice(..), begin, count);
1184+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1185+ }
1186+ }
1187+ }
1188+
1189+ #[test]
1190+ fn test_behaviour_when_moving_to_start_of_previous_long_words() {
1191+ let tests = [
1192+ (
1193+ "Basic backward motion from the middle of a word",
1194+ vec![(1, Range::new(3, 3), Range::new(4, 0))],
1195+ ),
1196+
1197+ // // Why do we want this behavior? The current behavior fails this
1198+ // // test, but seems better and more consistent.
1199+ // ("Starting from after boundary retreats the anchor",
1200+ // vec![(1, Range::new(0, 9), Range::new(8, 0))]),
1201+
1202+ (
1203+ " Jump to start of a word preceded by whitespace",
1204+ vec![(1, Range::new(5, 5), Range::new(6, 4))],
1205+ ),
1206+ (
1207+ " Jump to start of line from start of word preceded by whitespace",
1208+ vec![(1, Range::new(3, 4), Range::new(4, 0))],
1209+ ),
1210+ ("Previous anchor is irrelevant for backward motions",
1211+ vec![(1, Range::new(12, 5), Range::new(6, 0))]),
1212+ (
1213+ " Starting from whitespace moves to first space in sequence",
1214+ vec![(1, Range::new(0, 4), Range::new(4, 0))],
1215+ ),
1216+ ("Identifiers_with_underscores are considered a single word",
1217+ vec![(1, Range::new(0, 20), Range::new(20, 0))]),
1218+ (
1219+ "Jumping\n \nback through a newline selects whitespace",
1220+ vec![(1, Range::new(0, 13), Range::new(12, 8))],
1221+ ),
1222+ (
1223+ "Jumping to start of word from the end selects the word",
1224+ vec![(1, Range::new(6, 7), Range::new(7, 0))],
1225+ ),
1226+ (
1227+ "alphanumeric.!,and.?=punctuation are treated exactly the same",
1228+ vec![(1, Range::new(29, 30), Range::new(30, 0))],
1229+ ),
1230+ (
1231+ "... ... punctuation and spaces behave as expected",
1232+ vec![
1233+ (1, Range::new(0, 10), Range::new(10, 6)),
1234+ (1, Range::new(10, 6), Range::new(6, 0)),
1235+ ],
1236+ ),
1237+ (".._.._ punctuation is joined by underscores into a single block",
1238+ vec![(1, Range::new(0, 6), Range::new(6, 0))]),
1239+ (
1240+ "Newlines\n\nare bridged seamlessly.",
1241+ vec![(1, Range::new(0, 10), Range::new(8, 0))],
1242+ ),
1243+ (
1244+ "Jumping \n\n\n\n\nback from within a newline group selects previous block",
1245+ vec![(1, Range::new(0, 13), Range::new(11, 0))],
1246+ ),
1247+ (
1248+ "Failed motions do not modify the range",
1249+ vec![(0, Range::new(3, 0), Range::new(3, 0))],
1250+ ),
1251+ (
1252+ "Multiple motions at once resolve correctly",
1253+ vec![(3, Range::new(19, 19), Range::new(9, 0))],
1254+ ),
1255+ (
1256+ "Excessive motions are performed partially",
1257+ vec![(999, Range::new(40, 40), Range::new(10, 0))],
1258+ ),
1259+ (
1260+ "", // Edge case of moving backwards in empty string
1261+ vec![(1, Range::new(0, 0), Range::new(0, 0))],
1262+ ),
1263+ (
1264+ "\n\n\n\n\n", // Edge case of moving backwards in all newlines
1265+ vec![(1, Range::new(5, 5), Range::new(0, 0))],
1266+ ),
1267+ (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1268+ vec![
1269+ (1, Range::new(0, 8), Range::new(7, 4)),
1270+ (1, Range::new(7, 4), Range::new(3, 0)),
1271+ ]),
1272+ ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
1273+ vec![
1274+ (1, Range::new(0, 8), Range::new(8, 0)),
1275+ ]),
1276+ ];
1277+
1278+ for (sample, scenario) in tests {
1279+ for (count, begin, expected_end) in scenario.into_iter() {
1280+ let range = move_prev_long_word_start(Rope::from(sample).slice(..), begin, count);
1281+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1282+ }
1283+ }
1284+ }
1285+
1286+ #[test]
1287+ fn test_behaviour_when_moving_to_end_of_next_words() {
1288+ let tests = [
1289+ ("Basic forward motion from the start of a word to the end of it",
1290+ vec![(1, Range::new(0, 0), Range::new(0, 5))]),
1291+ ("Basic forward motion from the end of a word to the end of the next",
1292+ vec![(1, Range::new(0, 5), Range::new(5, 13))]),
1293+ ("Basic forward motion from the middle of a word to the end of it",
1294+ vec![(1, Range::new(2, 2), Range::new(2, 5))]),
1295+ (" Jumping to end of a word preceded by whitespace",
1296+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
1297+
1298+ // // Why do we want this behavior? The current behavior fails this
1299+ // // test, but seems better and more consistent.
1300+ // (" Starting from a boundary advances the anchor",
1301+ // vec![(1, Range::new(0, 0), Range::new(1, 9))]),
1302+
1303+ ("Previous anchor is irrelevant for end of word motion",
1304+ vec![(1, Range::new(12, 2), Range::new(2, 8))]),
1305+ ("Identifiers_with_underscores are considered a single word",
1306+ vec![(1, Range::new(0, 0), Range::new(0, 28))]),
1307+ ("Jumping\n into starting whitespace selects up to the end of next word",
1308+ vec![(1, Range::new(0, 7), Range::new(8, 16))]),
1309+ ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
1310+ vec![
1311+ (1, Range::new(0, 0), Range::new(0, 12)),
1312+ (1, Range::new(0, 12), Range::new(12, 15)),
1313+ (1, Range::new(12, 15), Range::new(15, 18))
1314+ ]),
1315+ ("... ... punctuation and spaces behave as expected",
1316+ vec![
1317+ (1, Range::new(0, 0), Range::new(0, 3)),
1318+ (1, Range::new(0, 3), Range::new(3, 9)),
1319+ ]),
1320+ (".._.._ punctuation is not joined by underscores into a single block",
1321+ vec![(1, Range::new(0, 0), Range::new(0, 2))]),
1322+ ("Newlines\n\nare bridged seamlessly.",
1323+ vec![
1324+ (1, Range::new(0, 0), Range::new(0, 8)),
1325+ (1, Range::new(0, 8), Range::new(10, 13)),
1326+ ]),
1327+ ("Jumping\n\n\n\n\n\n from newlines to whitespace selects to end of next word.",
1328+ vec![
1329+ (1, Range::new(0, 8), Range::new(13, 20)),
1330+ ]),
1331+ ("A failed motion does not modify the range",
1332+ vec![
1333+ (3, Range::new(37, 41), Range::new(37, 41)),
1334+ ]),
1335+ ("Multiple motions at once resolve correctly",
1336+ vec![
1337+ (3, Range::new(0, 0), Range::new(16, 19)),
1338+ ]),
1339+ ("Excessive motions are performed partially",
1340+ vec![
1341+ (999, Range::new(0, 0), Range::new(31, 41)),
1342+ ]),
1343+ ("", // Edge case of moving forward in empty string
1344+ vec![
1345+ (1, Range::new(0, 0), Range::new(0, 0)),
1346+ ]),
1347+ ("\n\n\n\n\n", // Edge case of moving forward in all newlines
1348+ vec![
1349+ (1, Range::new(0, 0), Range::new(5, 5)),
1350+ ]),
1351+ ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
1352+ vec![
1353+ (1, Range::new(0, 0), Range::new(1, 4)),
1354+ (1, Range::new(1, 4), Range::new(5, 8)),
1355+ ]),
1356+ ("ヒーリクス multibyte characters behave as normal characters",
1357+ vec![
1358+ (1, Range::new(0, 0), Range::new(0, 5)),
1359+ ]),
1360+ ];
1361+
1362+ for (sample, scenario) in tests {
1363+ for (count, begin, expected_end) in scenario.into_iter() {
1364+ let range = move_next_word_end(Rope::from(sample).slice(..), begin, count);
1365+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1366+ }
1367+ }
1368+ }
1369+
1370+ #[test]
1371+ fn test_behaviour_when_moving_to_end_of_previous_words() {
1372+ let tests = [
1373+ ("Basic backward motion from the middle of a word",
1374+ vec![(1, Range::new(9, 9), Range::new(10, 5))]),
1375+ ("Starting from after boundary retreats the anchor",
1376+ vec![(1, Range::new(0, 14), Range::new(13, 8))]),
1377+ ("Jump to end of a word succeeded by whitespace",
1378+ vec![(1, Range::new(11, 11), Range::new(11, 4))]),
1379+ (" Jump to start of line from end of word preceded by whitespace",
1380+ vec![(1, Range::new(8, 8), Range::new(8, 0))]),
1381+ ("Previous anchor is irrelevant for backward motions",
1382+ vec![(1, Range::new(26, 12), Range::new(13, 8))]),
1383+ (" Starting from whitespace moves to first space in sequence",
1384+ vec![(1, Range::new(0, 4), Range::new(4, 0))]),
1385+ ("Test identifiers_with_underscores are considered a single word",
1386+ vec![(1, Range::new(0, 25), Range::new(25, 4))]),
1387+ ("Jumping\n \nback through a newline selects whitespace",
1388+ vec![(1, Range::new(0, 13), Range::new(12, 8))]),
1389+ ("Jumping to start of word from the end selects the whole word",
1390+ vec![(1, Range::new(16, 16), Range::new(16, 10))]),
1391+ ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
1392+ vec![
1393+ (1, Range::new(30, 30), Range::new(31, 21)),
1394+ (1, Range::new(31, 21), Range::new(21, 18)),
1395+ (1, Range::new(21, 18), Range::new(18, 15))
1396+ ]),
1397+
1398+ ("... ... punctuation and spaces behave as expected",
1399+ vec![
1400+ (1, Range::new(0, 10), Range::new(9, 3)),
1401+ (1, Range::new(9, 3), Range::new(3, 0)),
1402+ ]),
1403+ (".._.._ punctuation is not joined by underscores into a single block",
1404+ vec![(1, Range::new(0, 5), Range::new(5, 3))]),
1405+ ("Newlines\n\nare bridged seamlessly.",
1406+ vec![
1407+ (1, Range::new(0, 10), Range::new(8, 0)),
1408+ ]),
1409+ ("Jumping \n\n\n\n\nback from within a newline group selects previous block",
1410+ vec![
1411+ (1, Range::new(0, 13), Range::new(11, 7)),
1412+ ]),
1413+ ("Failed motions do not modify the range",
1414+ vec![
1415+ (0, Range::new(3, 0), Range::new(3, 0)),
1416+ ]),
1417+ ("Multiple motions at once resolve correctly",
1418+ vec![
1419+ (3, Range::new(24, 24), Range::new(16, 8)),
1420+ ]),
1421+ ("Excessive motions are performed partially",
1422+ vec![
1423+ (999, Range::new(40, 40), Range::new(9, 0)),
1424+ ]),
1425+ ("", // Edge case of moving backwards in empty string
1426+ vec![
1427+ (1, Range::new(0, 0), Range::new(0, 0)),
1428+ ]),
1429+ ("\n\n\n\n\n", // Edge case of moving backwards in all newlines
1430+ vec![
1431+ (1, Range::new(5, 5), Range::new(0, 0)),
1432+ ]),
1433+ (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1434+ vec![
1435+ (1, Range::new(0, 8), Range::new(7, 4)),
1436+ (1, Range::new(7, 4), Range::new(3, 0)),
1437+ ]),
1438+ ("Test ヒーリクス multibyte characters behave as normal characters",
1439+ vec![
1440+ (1, Range::new(0, 10), Range::new(10, 4)),
1441+ ]),
1442+ ];
1443+
1444+ for (sample, scenario) in tests {
1445+ for (count, begin, expected_end) in scenario.into_iter() {
1446+ let range = move_prev_word_end(Rope::from(sample).slice(..), begin, count);
1447+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1448+ }
1449+ }
1450+ }
1451+
1452+ #[test]
1453+ fn test_behaviour_when_moving_to_end_of_next_long_words() {
1454+ let tests = [
1455+ ("Basic forward motion from the start of a word to the end of it",
1456+ vec![(1, Range::new(0, 0), Range::new(0, 5))]),
1457+ ("Basic forward motion from the end of a word to the end of the next",
1458+ vec![(1, Range::new(0, 5), Range::new(5, 13))]),
1459+ ("Basic forward motion from the middle of a word to the end of it",
1460+ vec![(1, Range::new(2, 2), Range::new(2, 5))]),
1461+ (" Jumping to end of a word preceded by whitespace",
1462+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
1463+
1464+ // // Why do we want this behavior? The current behavior fails this
1465+ // // test, but seems better and more consistent.
1466+ // (" Starting from a boundary advances the anchor",
1467+ // vec![(1, Range::new(0, 0), Range::new(1, 9))]),
1468+
1469+ ("Previous anchor is irrelevant for end of word motion",
1470+ vec![(1, Range::new(12, 2), Range::new(2, 8))]),
1471+ ("Identifiers_with_underscores are considered a single word",
1472+ vec![(1, Range::new(0, 0), Range::new(0, 28))]),
1473+ ("Jumping\n into starting whitespace selects up to the end of next word",
1474+ vec![(1, Range::new(0, 7), Range::new(8, 16))]),
1475+ ("alphanumeric.!,and.?=punctuation are treated the same way",
1476+ vec![
1477+ (1, Range::new(0, 0), Range::new(0, 32)),
1478+ ]),
1479+ ("... ... punctuation and spaces behave as expected",
1480+ vec![
1481+ (1, Range::new(0, 0), Range::new(0, 3)),
1482+ (1, Range::new(0, 3), Range::new(3, 9)),
1483+ ]),
1484+ (".._.._ punctuation is joined by underscores into a single block",
1485+ vec![(1, Range::new(0, 0), Range::new(0, 6))]),
1486+ ("Newlines\n\nare bridged seamlessly.",
1487+ vec![
1488+ (1, Range::new(0, 0), Range::new(0, 8)),
1489+ (1, Range::new(0, 8), Range::new(10, 13)),
1490+ ]),
1491+ ("Jumping\n\n\n\n\n\n from newlines to whitespace selects to end of next word.",
1492+ vec![
1493+ (1, Range::new(0, 9), Range::new(13, 20)),
1494+ ]),
1495+ ("A failed motion does not modify the range",
1496+ vec![
1497+ (3, Range::new(37, 41), Range::new(37, 41)),
1498+ ]),
1499+ ("Multiple motions at once resolve correctly",
1500+ vec![
1501+ (3, Range::new(0, 0), Range::new(16, 19)),
1502+ ]),
1503+ ("Excessive motions are performed partially",
1504+ vec![
1505+ (999, Range::new(0, 0), Range::new(31, 41)),
1506+ ]),
1507+ ("", // Edge case of moving forward in empty string
1508+ vec![
1509+ (1, Range::new(0, 0), Range::new(0, 0)),
1510+ ]),
1511+ ("\n\n\n\n\n", // Edge case of moving forward in all newlines
1512+ vec![
1513+ (1, Range::new(0, 0), Range::new(5, 5)),
1514+ ]),
1515+ ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
1516+ vec![
1517+ (1, Range::new(0, 0), Range::new(1, 4)),
1518+ (1, Range::new(1, 4), Range::new(5, 8)),
1519+ ]),
1520+ ("ヒーリ..クス multibyte characters behave as normal characters, including when they interact with punctuation",
1521+ vec![
1522+ (1, Range::new(0, 0), Range::new(0, 7)),
1523+ ]),
1524+ ];
1525+
1526+ for (sample, scenario) in tests {
1527+ for (count, begin, expected_end) in scenario.into_iter() {
1528+ let range = move_next_long_word_end(Rope::from(sample).slice(..), begin, count);
1529+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1530+ }
1531+ }
1532+ }
1533+
1534+ #[test]
1535+ fn test_behaviour_when_moving_to_end_of_prev_long_words() {
1536+ let tests = [
1537+ (
1538+ "Basic backward motion from the middle of a word",
1539+ vec![(1, Range::new(3, 3), Range::new(4, 0))],
1540+ ),
1541+ ("Starting from after boundary retreats the anchor",
1542+ vec![(1, Range::new(0, 9), Range::new(8, 0))],
1543+ ),
1544+ (
1545+ "Jump to end of a word succeeded by whitespace",
1546+ vec![(1, Range::new(10, 10), Range::new(10, 4))],
1547+ ),
1548+ (
1549+ " Jump to start of line from end of word preceded by whitespace",
1550+ vec![(1, Range::new(3, 4), Range::new(4, 0))],
1551+ ),
1552+ ("Previous anchor is irrelevant for backward motions",
1553+ vec![(1, Range::new(12, 5), Range::new(6, 0))]),
1554+ (
1555+ " Starting from whitespace moves to first space in sequence",
1556+ vec![(1, Range::new(0, 4), Range::new(4, 0))],
1557+ ),
1558+ ("Identifiers_with_underscores are considered a single word",
1559+ vec![(1, Range::new(0, 20), Range::new(20, 0))]),
1560+ (
1561+ "Jumping\n \nback through a newline selects whitespace",
1562+ vec![(1, Range::new(0, 13), Range::new(12, 8))],
1563+ ),
1564+ (
1565+ "Jumping to start of word from the end selects the word",
1566+ vec![(1, Range::new(6, 7), Range::new(7, 0))],
1567+ ),
1568+ (
1569+ "alphanumeric.!,and.?=punctuation are treated exactly the same",
1570+ vec![(1, Range::new(29, 30), Range::new(30, 0))],
1571+ ),
1572+ (
1573+ "... ... punctuation and spaces behave as expected",
1574+ vec![
1575+ (1, Range::new(0, 10), Range::new(9, 3)),
1576+ (1, Range::new(10, 6), Range::new(7, 3)),
1577+ ],
1578+ ),
1579+ (".._.._ punctuation is joined by underscores into a single block",
1580+ vec![(1, Range::new(0, 6), Range::new(6, 0))]),
1581+ (
1582+ "Newlines\n\nare bridged seamlessly.",
1583+ vec![(1, Range::new(0, 10), Range::new(8, 0))],
1584+ ),
1585+ (
1586+ "Jumping \n\n\n\n\nback from within a newline group selects previous block",
1587+ vec![(1, Range::new(0, 13), Range::new(11, 7))],
1588+ ),
1589+ (
1590+ "Failed motions do not modify the range",
1591+ vec![(0, Range::new(3, 0), Range::new(3, 0))],
1592+ ),
1593+ (
1594+ "Multiple motions at once resolve correctly",
1595+ vec![(3, Range::new(19, 19), Range::new(8, 0))],
1596+ ),
1597+ (
1598+ "Excessive motions are performed partially",
1599+ vec![(999, Range::new(40, 40), Range::new(9, 0))],
1600+ ),
1601+ (
1602+ "", // Edge case of moving backwards in empty string
1603+ vec![(1, Range::new(0, 0), Range::new(0, 0))],
1604+ ),
1605+ (
1606+ "\n\n\n\n\n", // Edge case of moving backwards in all newlines
1607+ vec![(1, Range::new(5, 5), Range::new(0, 0))],
1608+ ),
1609+ (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1610+ vec![
1611+ (1, Range::new(0, 8), Range::new(7, 4)),
1612+ (1, Range::new(7, 4), Range::new(3, 0)),
1613+ ]),
1614+ ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
1615+ vec![
1616+ (1, Range::new(0, 8), Range::new(7, 0)),
1617+ ]),
1618+ ];
1619+
1620+ for (sample, scenario) in tests {
1621+ for (count, begin, expected_end) in scenario.into_iter() {
1622+ let range = move_prev_long_word_end(Rope::from(sample).slice(..), begin, count);
1623+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1624+ }
1625+ }
1626+ }
1627+
1628+ #[test]
1629+ fn test_behaviour_when_moving_to_prev_paragraph_single() {
1630+ let tests = [
1631+ ("#[|]#", "#[|]#"),
1632+ ("#[s|]#tart at\nfirst char\n", "#[|s]#tart at\nfirst char\n"),
1633+ ("start at\nlast char#[\n|]#", "#[|start at\nlast char\n]#"),
1634+ (
1635+ "goto\nfirst\n\n#[p|]#aragraph",
1636+ "#[|goto\nfirst\n\n]#paragraph",
1637+ ),
1638+ (
1639+ "goto\nfirst\n#[\n|]#paragraph",
1640+ "#[|goto\nfirst\n\n]#paragraph",
1641+ ),
1642+ (
1643+ "goto\nsecond\n\np#[a|]#ragraph",
1644+ "goto\nsecond\n\n#[|pa]#ragraph",
1645+ ),
1646+ (
1647+ "here\n\nhave\nmultiple\nparagraph\n\n\n\n\n#[|]#",
1648+ "here\n\n#[|have\nmultiple\nparagraph\n\n\n\n\n]#",
1649+ ),
1650+ ];
1651+
1652+ for (before, expected) in tests {
1653+ let (s, selection) = crate::test::print(before);
1654+ let text = Rope::from(s.as_str());
1655+ let selection =
1656+ selection.transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Move));
1657+ let actual = crate::test::plain(s.as_ref(), &selection);
1658+ assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1659+ }
1660+ }
1661+
1662+ #[test]
1663+ fn test_behaviour_when_moving_to_prev_paragraph_double() {
1664+ let tests = [
1665+ (
1666+ "on#[e|]#\n\ntwo\n\nthree\n\n",
1667+ "#[|one]#\n\ntwo\n\nthree\n\n",
1668+ ),
1669+ (
1670+ "one\n\ntwo\n\nth#[r|]#ee\n\n",
1671+ "one\n\n#[|two\n\nthr]#ee\n\n",
1672+ ),
1673+ ];
1674+
1675+ for (before, expected) in tests {
1676+ let (s, selection) = crate::test::print(before);
1677+ let text = Rope::from(s.as_str());
1678+ let selection =
1679+ selection.transform(|r| move_prev_paragraph(text.slice(..), r, 2, Movement::Move));
1680+ let actual = crate::test::plain(s.as_ref(), &selection);
1681+ assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1682+ }
1683+ }
1684+
1685+ #[test]
1686+ fn test_behaviour_when_moving_to_prev_paragraph_extend() {
1687+ let tests = [
1688+ (
1689+ "one\n\n#[|two\n\n]#three\n\n",
1690+ "#[|one\n\ntwo\n\n]#three\n\n",
1691+ ),
1692+ (
1693+ "#[|one\n\ntwo\n\n]#three\n\n",
1694+ "#[|one\n\ntwo\n\n]#three\n\n",
1695+ ),
1696+ ];
1697+
1698+ for (before, expected) in tests {
1699+ let (s, selection) = crate::test::print(before);
1700+ let text = Rope::from(s.as_str());
1701+ let selection = selection
1702+ .transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Extend));
1703+ let actual = crate::test::plain(s.as_ref(), &selection);
1704+ assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1705+ }
1706+ }
1707+
1708+ #[test]
1709+ fn test_behaviour_when_moving_to_next_paragraph_single() {
1710+ let tests = [
1711+ ("#[|]#", "#[|]#"),
1712+ ("#[s|]#tart at\nfirst char\n", "#[start at\nfirst char\n|]#"),
1713+ ("start at\nlast char#[\n|]#", "start at\nlast char#[\n|]#"),
1714+ (
1715+ "a\nb\n\n#[g|]#oto\nthird\n\nparagraph",
1716+ "a\nb\n\n#[goto\nthird\n\n|]#paragraph",
1717+ ),
1718+ (
1719+ "a\nb\n#[\n|]#goto\nthird\n\nparagraph",
1720+ "a\nb\n\n#[goto\nthird\n\n|]#paragraph",
1721+ ),
1722+ (
1723+ "a\nb#[\n|]#\n\ngoto\nsecond\n\nparagraph",
1724+ "a\nb#[\n\n|]#goto\nsecond\n\nparagraph",
1725+ ),
1726+ (
1727+ "here\n\nhave\n#[m|]#ultiple\nparagraph\n\n\n\n\n",
1728+ "here\n\nhave\n#[multiple\nparagraph\n\n\n\n\n|]#",
1729+ ),
1730+ (
1731+ "#[t|]#ext\n\n\nafter two blank lines\n\nmore text\n",
1732+ "#[text\n\n\n|]#after two blank lines\n\nmore text\n",
1733+ ),
1734+ (
1735+ "#[text\n\n\n|]#after two blank lines\n\nmore text\n",
1736+ "text\n\n\n#[after two blank lines\n\n|]#more text\n",
1737+ ),
1738+ ];
1739+
1740+ for (before, expected) in tests {
1741+ let (s, selection) = crate::test::print(before);
1742+ let text = Rope::from(s.as_str());
1743+ let selection =
1744+ selection.transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Move));
1745+ let actual = crate::test::plain(s.as_ref(), &selection);
1746+ assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1747+ }
1748+ }
1749+
1750+ #[test]
1751+ fn test_behaviour_when_moving_to_next_paragraph_double() {
1752+ let tests = [
1753+ (
1754+ "one\n\ntwo\n\nth#[r|]#ee\n\n",
1755+ "one\n\ntwo\n\nth#[ree\n\n|]#",
1756+ ),
1757+ (
1758+ "on#[e|]#\n\ntwo\n\nthree\n\n",
1759+ "on#[e\n\ntwo\n\n|]#three\n\n",
1760+ ),
1761+ ];
1762+
1763+ for (before, expected) in tests {
1764+ let (s, selection) = crate::test::print(before);
1765+ let text = Rope::from(s.as_str());
1766+ let selection =
1767+ selection.transform(|r| move_next_paragraph(text.slice(..), r, 2, Movement::Move));
1768+ let actual = crate::test::plain(s.as_ref(), &selection);
1769+ assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1770+ }
1771+ }
1772+
1773+ #[test]
1774+ fn test_behaviour_when_moving_to_next_paragraph_extend() {
1775+ let tests = [
1776+ (
1777+ "one\n\n#[two\n\n|]#three\n\n",
1778+ "one\n\n#[two\n\nthree\n\n|]#",
1779+ ),
1780+ (
1781+ "one\n\n#[two\n\nthree\n\n|]#",
1782+ "one\n\n#[two\n\nthree\n\n|]#",
1783+ ),
1784+ ];
1785+
1786+ for (before, expected) in tests {
1787+ let (s, selection) = crate::test::print(before);
1788+ let text = Rope::from(s.as_str());
1789+ let selection = selection
1790+ .transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Extend));
1791+ let actual = crate::test::plain(s.as_ref(), &selection);
1792+ assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1793+ }
1794+ }
1795+}
helix-core/src/object.rsnew file+76
@@ -0,0 +1,76 @@
1+use crate::{Range, RopeSlice, Selection, Syntax};
2+use tree_sitter::Node;
3+
4+pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
5+ select_node_impl(syntax, text, selection, |mut node, from, to| {
6+ while node.start_byte() == from && node.end_byte() == to {
7+ node = node.parent()?;
8+ }
9+ Some(node)
10+ })
11+}
12+
13+pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
14+ select_node_impl(syntax, text, selection, |descendant, _from, _to| {
15+ descendant.child(0).or(Some(descendant))
16+ })
17+}
18+
19+pub fn select_sibling<F>(
20+ syntax: &Syntax,
21+ text: RopeSlice,
22+ selection: Selection,
23+ sibling_fn: &F,
24+) -> Selection
25+where
26+ F: Fn(Node) -> Option<Node>,
27+{
28+ select_node_impl(syntax, text, selection, |descendant, _from, _to| {
29+ find_sibling_recursive(descendant, sibling_fn)
30+ })
31+}
32+
33+fn find_sibling_recursive<F>(node: Node, sibling_fn: F) -> Option<Node>
34+where
35+ F: Fn(Node) -> Option<Node>,
36+{
37+ sibling_fn(node).or_else(|| {
38+ node.parent()
39+ .and_then(|node| find_sibling_recursive(node, sibling_fn))
40+ })
41+}
42+
43+fn select_node_impl<F>(
44+ syntax: &Syntax,
45+ text: RopeSlice,
46+ selection: Selection,
47+ select_fn: F,
48+) -> Selection
49+where
50+ F: Fn(Node, usize, usize) -> Option<Node>,
51+{
52+ let tree = syntax.tree();
53+
54+ selection.transform(|range| {
55+ let from = text.char_to_byte(range.from());
56+ let to = text.char_to_byte(range.to());
57+
58+ let node = match tree
59+ .root_node()
60+ .descendant_for_byte_range(from, to)
61+ .and_then(|node| select_fn(node, from, to))
62+ {
63+ Some(node) => node,
64+ None => return range,
65+ };
66+
67+ let from = text.byte_to_char(node.start_byte());
68+ let to = text.byte_to_char(node.end_byte());
69+
70+ if range.head < range.anchor {
71+ Range::new(to, from)
72+ } else {
73+ Range::new(from, to)
74+ }
75+ })
76+}
helix-core/src/path.rsnew file+162
@@ -0,0 +1,162 @@
1+use etcetera::home_dir;
2+use std::path::{Component, Path, PathBuf};
3+
4+/// Replaces users home directory from `path` with tilde `~` if the directory
5+/// is available, otherwise returns the path unchanged.
6+pub fn fold_home_dir(path: &Path) -> PathBuf {
7+ if let Ok(home) = home_dir() {
8+ if let Ok(stripped) = path.strip_prefix(&home) {
9+ return PathBuf::from("~").join(stripped);
10+ }
11+ }
12+
13+ path.to_path_buf()
14+}
15+
16+/// Expands tilde `~` into users home directory if available, otherwise returns the path
17+/// unchanged. The tilde will only be expanded when present as the first component of the path
18+/// and only slash follows it.
19+pub fn expand_tilde(path: &Path) -> PathBuf {
20+ let mut components = path.components().peekable();
21+ if let Some(Component::Normal(c)) = components.peek() {
22+ if c == &"~" {
23+ if let Ok(home) = home_dir() {
24+ // it's ok to unwrap, the path starts with `~`
25+ return home.join(path.strip_prefix("~").unwrap());
26+ }
27+ }
28+ }
29+
30+ path.to_path_buf()
31+}
32+
33+/// Normalize a path, removing things like `.` and `..`.
34+///
35+/// CAUTION: This does not resolve symlinks (unlike
36+/// [`std::fs::canonicalize`]). This may cause incorrect or surprising
37+/// behavior at times. This should be used carefully. Unfortunately,
38+/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often
39+/// fail, or on Windows returns annoying device paths. This is a problem Cargo
40+/// needs to improve on.
41+/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
42+pub fn get_normalized_path(path: &Path) -> PathBuf {
43+ // normalization strategy is to canonicalize first ancestor path that exists (i.e., canonicalize as much as possible),
44+ // then run handrolled normalization on the non-existent remainder
45+ let (base, path) = path
46+ .ancestors()
47+ .find_map(|base| {
48+ let canonicalized_base = dunce::canonicalize(base).ok()?;
49+ let remainder = path.strip_prefix(base).ok()?.into();
50+ Some((canonicalized_base, remainder))
51+ })
52+ .unwrap_or_else(|| (PathBuf::new(), PathBuf::from(path)));
53+
54+ if path.as_os_str().is_empty() {
55+ return base;
56+ }
57+
58+ let mut components = path.components().peekable();
59+ let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
60+ components.next();
61+ PathBuf::from(c.as_os_str())
62+ } else {
63+ PathBuf::new()
64+ };
65+
66+ for component in components {
67+ match component {
68+ Component::Prefix(..) => unreachable!(),
69+ Component::RootDir => {
70+ ret.push(component.as_os_str());
71+ }
72+ Component::CurDir => {}
73+ Component::ParentDir => {
74+ ret.pop();
75+ }
76+ Component::Normal(c) => {
77+ ret.push(c);
78+ }
79+ }
80+ }
81+ base.join(ret)
82+}
83+
84+/// Returns the canonical, absolute form of a path with all intermediate components normalized.
85+///
86+/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
87+/// here if the path exists, just normalize it's components.
88+pub fn get_canonicalized_path(path: &Path) -> PathBuf {
89+ let path = expand_tilde(path);
90+ let path = if path.is_relative() {
91+ helix_loader::current_working_dir().join(path)
92+ } else {
93+ path
94+ };
95+
96+ get_normalized_path(path.as_path())
97+}
98+
99+pub fn get_relative_path(path: &Path) -> PathBuf {
100+ let path = PathBuf::from(path);
101+ let path = if path.is_absolute() {
102+ let cwdir = get_normalized_path(&helix_loader::current_working_dir());
103+ get_normalized_path(&path)
104+ .strip_prefix(cwdir)
105+ .map(PathBuf::from)
106+ .unwrap_or(path)
107+ } else {
108+ path
109+ };
110+ fold_home_dir(&path)
111+}
112+
113+/// Returns a truncated filepath where the basepart of the path is reduced to the first
114+/// char of the folder and the whole filename appended.
115+///
116+/// Also strip the current working directory from the beginning of the path.
117+/// Note that this function does not check if the truncated path is unambiguous.
118+///
119+/// ```
120+/// use helix_core::path::get_truncated_path;
121+/// use std::path::Path;
122+///
123+/// assert_eq!(
124+/// get_truncated_path("/home/cnorris/documents/jokes.txt").as_path(),
125+/// Path::new("/h/c/d/jokes.txt")
126+/// );
127+/// assert_eq!(
128+/// get_truncated_path("jokes.txt").as_path(),
129+/// Path::new("jokes.txt")
130+/// );
131+/// assert_eq!(
132+/// get_truncated_path("/jokes.txt").as_path(),
133+/// Path::new("/jokes.txt")
134+/// );
135+/// assert_eq!(
136+/// get_truncated_path("/h/c/d/jokes.txt").as_path(),
137+/// Path::new("/h/c/d/jokes.txt")
138+/// );
139+/// assert_eq!(get_truncated_path("").as_path(), Path::new(""));
140+/// ```
141+///
142+pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
143+ let cwd = helix_loader::current_working_dir();
144+ let path = path
145+ .as_ref()
146+ .strip_prefix(cwd)
147+ .unwrap_or_else(|_| path.as_ref());
148+ let file = path.file_name().unwrap_or_default();
149+ let base = path.parent().unwrap_or_else(|| Path::new(""));
150+ let mut ret = PathBuf::new();
151+ for d in base {
152+ ret.push(
153+ d.to_string_lossy()
154+ .chars()
155+ .next()
156+ .unwrap_or_default()
157+ .to_string(),
158+ );
159+ }
160+ ret.push(file);
161+ ret
162+}
helix-core/src/position.rs+808−6
@@ -1,3 +1,14 @@
1+use std::{borrow::Cow, cmp::Ordering};
2+
3+use crate::{
4+ chars::char_is_line_ending,
5+ doc_formatter::{DocumentFormatter, TextFormat},
6+ graphemes::{ensure_grapheme_boundary_prev, grapheme_width, RopeGraphemes},
7+ line_ending::line_end_char_index,
8+ text_annotations::TextAnnotations,
9+ RopeSlice,
10+};
11+
1/// Represents a single point in a text buffer. Zero indexed.
12/// Represents a single point in a text buffer. Zero indexed.
2#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3pub struct Position {
14pub struct Position {
@@ -18,8 +29,9 @@ impl Position {
18 pub fn traverse(self, text: &crate::Tendril) -> Self {
29 pub fn traverse(self, text: &crate::Tendril) -> Self {
19 let Self { mut row, mut col } = self;
30 let Self { mut row, mut col } = self;
20 // TODO: there should be a better way here
31 // TODO: there should be a better way here
21− for ch in text.chars() {
32+ let mut chars = text.chars().peekable();
22− if ch == '\n' {
33+ while let Some(ch) = chars.next() {
34+ if char_is_line_ending(ch) && !(ch == '\r' && chars.peek() == Some(&'\n')) {
23 row += 1;
35 row += 1;
24 col = 0;
36 col = 0;
25 } else {
37 } else {
@@ -32,26 +44,816 @@ impl Position {
32
44
33impl From<(usize, usize)> for Position {
45impl From<(usize, usize)> for Position {
34 fn from(tuple: (usize, usize)) -> Self {
46 fn from(tuple: (usize, usize)) -> Self {
35− Position {
47+ Self {
36 row: tuple.0,
48 row: tuple.0,
37 col: tuple.1,
49 col: tuple.1,
38 }
50 }
39 }
51 }
40}
52}
41
53
42−impl Into<tree_sitter::Point> for Position {
54+impl From<Position> for tree_sitter::Point {
43− fn into(self) -> tree_sitter::Point {
55+ fn from(pos: Position) -> Self {
44− tree_sitter::Point::new(self.row, self.col)
56+ Self::new(pos.row, pos.col)
57+ }
58+}
59+/// Convert a character index to (line, column) coordinates.
60+///
61+/// column in `char` count which can be used for row:column display in
62+/// status line. See [`visual_coords_at_pos`] for a visual one.
63+pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
64+ let line = text.char_to_line(pos);
65+
66+ let line_start = text.line_to_char(line);
67+ let pos = ensure_grapheme_boundary_prev(text, pos);
68+ let col = RopeGraphemes::new(text.slice(line_start..pos)).count();
69+
70+ Position::new(line, col)
71+}
72+
73+/// Convert a character index to (line, column) coordinates visually.
74+///
75+/// Takes \t, double-width characters (CJK) into account as well as text
76+/// not in the document in the future.
77+/// See [`coords_at_pos`] for an "objective" one.
78+///
79+/// This function should be used very rarely. Usually `visual_offset_from_anchor`
80+/// or `visual_offset_from_block` is preferable. However when you want to compute the
81+/// actual visual row/column in the text (not what is actually shown on screen)
82+/// then you should use this function. For example aligning text should ignore virtual
83+/// text and softwrap.
84+#[deprecated = "Doesn't account for softwrap or decorations, use visual_offset_from_anchor instead"]
85+pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Position {
86+ let line = text.char_to_line(pos);
87+
88+ let line_start = text.line_to_char(line);
89+ let pos = ensure_grapheme_boundary_prev(text, pos);
90+
91+ let mut col = 0;
92+
93+ for grapheme in RopeGraphemes::new(text.slice(line_start..pos)) {
94+ if grapheme == "\t" {
95+ col += tab_width - (col % tab_width);
96+ } else {
97+ let grapheme = Cow::from(grapheme);
98+ col += grapheme_width(&grapheme);
99+ }
100+ }
101+
102+ Position::new(line, col)
103+}
104+
105+/// Returns the visual offset from the start of the first visual line
106+/// in the block that contains anchor.
107+/// Text is always wrapped at blocks, they usually correspond to
108+/// actual line breaks but for very long lines
109+/// softwrapping positions are estimated with an O(1) algorithm
110+/// to ensure consistent performance for large lines (currently unimplemented)
111+///
112+/// Usually you want to use `visual_offset_from_anchor` instead but this function
113+/// can be useful (and faster) if
114+/// * You already know the visual position of the block
115+/// * You only care about the horizontal offset (column) and not the vertical offset (row)
116+pub fn visual_offset_from_block(
117+ text: RopeSlice,
118+ anchor: usize,
119+ pos: usize,
120+ text_fmt: &TextFormat,
121+ annotations: &TextAnnotations,
122+) -> (Position, usize) {
123+ let mut last_pos = Position::default();
124+ let (formatter, block_start) =
125+ DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
126+ let mut char_pos = block_start;
127+
128+ for (grapheme, vpos) in formatter {
129+ last_pos = vpos;
130+ char_pos += grapheme.doc_chars();
131+
132+ if char_pos > pos {
133+ return (last_pos, block_start);
134+ }
135+ }
136+
137+ (last_pos, block_start)
138+}
139+
140+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
141+pub enum VisualOffsetError {
142+ PosBeforeAnchorRow,
143+ PosAfterMaxRow,
144+}
145+
146+/// Returns the visual offset from the start of the visual line
147+/// that contains anchor.
148+pub fn visual_offset_from_anchor(
149+ text: RopeSlice,
150+ anchor: usize,
151+ pos: usize,
152+ text_fmt: &TextFormat,
153+ annotations: &TextAnnotations,
154+ max_rows: usize,
155+) -> Result<(Position, usize), VisualOffsetError> {
156+ let (formatter, block_start) =
157+ DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
158+ let mut char_pos = block_start;
159+ let mut anchor_line = None;
160+ let mut found_pos = None;
161+ let mut last_pos = Position::default();
162+
163+ if pos < block_start {
164+ return Err(VisualOffsetError::PosBeforeAnchorRow);
165+ }
166+
167+ for (grapheme, vpos) in formatter {
168+ last_pos = vpos;
169+ char_pos += grapheme.doc_chars();
170+
171+ if char_pos > pos {
172+ if let Some(anchor_line) = anchor_line {
173+ last_pos.row -= anchor_line;
174+ return Ok((last_pos, block_start));
175+ } else {
176+ found_pos = Some(last_pos);
177+ }
178+ }
179+ if char_pos > anchor && anchor_line.is_none() {
180+ if let Some(mut found_pos) = found_pos {
181+ return if found_pos.row == last_pos.row {
182+ found_pos.row = 0;
183+ Ok((found_pos, block_start))
184+ } else {
185+ Err(VisualOffsetError::PosBeforeAnchorRow)
186+ };
187+ } else {
188+ anchor_line = Some(last_pos.row);
189+ }
190+ }
191+
192+ if let Some(anchor_line) = anchor_line {
193+ if vpos.row >= anchor_line + max_rows {
194+ return Err(VisualOffsetError::PosAfterMaxRow);
195+ }
196+ }
197+ }
198+
199+ let anchor_line = anchor_line.unwrap_or(last_pos.row);
200+ last_pos.row -= anchor_line;
201+
202+ Ok((last_pos, block_start))
203+}
204+
205+/// Convert (line, column) coordinates to a character index.
206+///
207+/// If the `line` coordinate is beyond the end of the file, the EOF
208+/// position will be returned.
209+///
210+/// If the `column` coordinate is past the end of the given line, the
211+/// line-end position will be returned. What constitutes the "line-end
212+/// position" depends on the parameter `limit_before_line_ending`. If it's
213+/// `true`, the line-end position will be just *before* the line ending
214+/// character. If `false` it will be just *after* the line ending
215+/// character--on the border between the current line and the next.
216+///
217+/// Usually you only want `limit_before_line_ending` to be `true` if you're working
218+/// with left-side block-cursor positions, as this prevents the the block cursor
219+/// from jumping to the next line. Otherwise you typically want it to be `false`,
220+/// such as when dealing with raw anchor/head positions.
221+pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending: bool) -> usize {
222+ let Position { mut row, col } = coords;
223+ if limit_before_line_ending {
224+ row = row.min(text.len_lines() - 1);
225+ };
226+ let line_start = text.line_to_char(row);
227+ let line_end = if limit_before_line_ending {
228+ line_end_char_index(&text, row)
229+ } else {
230+ text.line_to_char((row + 1).min(text.len_lines()))
231+ };
232+
233+ let mut col_char_offset = 0;
234+ for (i, g) in RopeGraphemes::new(text.slice(line_start..line_end)).enumerate() {
235+ if i == col {
236+ break;
237+ }
238+ col_char_offset += g.chars().count();
239+ }
240+
241+ line_start + col_char_offset
242+}
243+
244+/// Convert visual (line, column) coordinates to a character index.
245+///
246+/// If the `line` coordinate is beyond the end of the file, the EOF
247+/// position will be returned.
248+///
249+/// If the `column` coordinate is past the end of the given line, the
250+/// line-end position (in this case, just before the line ending
251+/// character) will be returned.
252+/// This function should be used very rarely. Usually `char_idx_at_visual_offset` is preferable.
253+/// However when you want to compute a char position from the visual row/column in the text
254+/// (not what is actually shown on screen) then you should use this function.
255+/// For example aligning text should ignore virtual text and softwrap.
256+#[deprecated = "Doesn't account for softwrap or decorations, use char_idx_at_visual_offset instead"]
257+pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize) -> usize {
258+ let Position { mut row, col } = coords;
259+ row = row.min(text.len_lines() - 1);
260+ let line_start = text.line_to_char(row);
261+ let line_end = line_end_char_index(&text, row);
262+
263+ let mut col_char_offset = 0;
264+ let mut cols_remaining = col;
265+ for grapheme in RopeGraphemes::new(text.slice(line_start..line_end)) {
266+ let grapheme_width = if grapheme == "\t" {
267+ tab_width - ((col - cols_remaining) % tab_width)
268+ } else {
269+ let grapheme = Cow::from(grapheme);
270+ grapheme_width(&grapheme)
271+ };
272+
273+ // If pos is in the middle of a wider grapheme (tab for example)
274+ // return the starting offset.
275+ if grapheme_width > cols_remaining {
276+ break;
277+ }
278+
279+ cols_remaining -= grapheme_width;
280+ col_char_offset += grapheme.chars().count();
281+ }
282+
283+ line_start + col_char_offset
284+}
285+
286+/// Returns the char index on the visual line `row_offset` below the visual line of
287+/// the provided char index `anchor` that is closest to the supplied visual `column`.
288+///
289+/// If the targeted visual line is entirely covered by virtual text the last
290+/// char position before the virtual text and a virtual offset is returned instead.
291+///
292+/// If no (text) grapheme starts at exactly at the specified column the
293+/// start of the grapheme to the left is returned. If there is no grapheme
294+/// to the left (for example if the line starts with virtual text) then the positioning
295+/// of the next grapheme to the right is returned.
296+///
297+/// If the `line` coordinate is beyond the end of the file, the EOF
298+/// position will be returned.
299+///
300+/// If the `column` coordinate is past the end of the given line, the
301+/// line-end position (in this case, just before the line ending
302+/// character) will be returned.
303+///
304+/// # Returns
305+///
306+/// `(real_char_idx, virtual_lines)`
307+///
308+/// The nearest character idx "closest" (see above) to the specified visual offset
309+/// on the visual line is returned if the visual line contains any text:
310+/// If the visual line at the specified offset is a virtual line generated by a `LineAnnotation`
311+/// the previous char_index is returned, together with the remaining vertical offset (`virtual_lines`)
312+pub fn char_idx_at_visual_offset(
313+ text: RopeSlice,
314+ mut anchor: usize,
315+ mut row_offset: isize,
316+ column: usize,
317+ text_fmt: &TextFormat,
318+ annotations: &TextAnnotations,
319+) -> (usize, usize) {
320+ let mut pos = anchor;
321+ // convert row relative to visual line containing anchor to row relative to a block containing anchor (anchor may change)
322+ loop {
323+ let (visual_pos_in_block, block_char_offset) =
324+ visual_offset_from_block(text, anchor, pos, text_fmt, annotations);
325+ row_offset += visual_pos_in_block.row as isize;
326+ anchor = block_char_offset;
327+ if row_offset >= 0 {
328+ break;
329+ }
330+
331+ if block_char_offset == 0 {
332+ row_offset = 0;
333+ break;
334+ }
335+ // the row_offset is negative so we need to look at the previous block
336+ // set the anchor to the last char before the current block so that we can compute
337+ // the distance of this block from the start of the previous block
338+ pos = anchor;
339+ anchor -= 1;
340+ }
341+
342+ char_idx_at_visual_block_offset(
343+ text,
344+ anchor,
345+ row_offset as usize,
346+ column,
347+ text_fmt,
348+ annotations,
349+ )
350+}
351+
352+/// This function behaves the same as `char_idx_at_visual_offset`, except that
353+/// the vertical offset `row` is always computed relative to the block that contains `anchor`
354+/// instead of the visual line that contains `anchor`.
355+/// Usually `char_idx_at_visual_offset` is more useful but this function can be
356+/// used in some situations as an optimization when `visual_offset_from_block` was used
357+///
358+/// # Returns
359+///
360+/// `(real_char_idx, virtual_lines)`
361+///
362+/// See `char_idx_at_visual_offset` for details
363+pub fn char_idx_at_visual_block_offset(
364+ text: RopeSlice,
365+ anchor: usize,
366+ row: usize,
367+ column: usize,
368+ text_fmt: &TextFormat,
369+ annotations: &TextAnnotations,
370+) -> (usize, usize) {
371+ let (formatter, mut char_idx) =
372+ DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
373+ let mut last_char_idx = char_idx;
374+ let mut last_char_idx_on_line = None;
375+ let mut last_row = 0;
376+ for (grapheme, grapheme_pos) in formatter {
377+ match grapheme_pos.row.cmp(&row) {
378+ Ordering::Equal => {
379+ if grapheme_pos.col + grapheme.width() > column {
380+ if !grapheme.is_virtual() {
381+ return (char_idx, 0);
382+ } else if let Some(char_idx) = last_char_idx_on_line {
383+ return (char_idx, 0);
384+ }
385+ } else if !grapheme.is_virtual() {
386+ last_char_idx_on_line = Some(char_idx)
387+ }
388+ }
389+ Ordering::Greater => return (last_char_idx, row - last_row),
390+ _ => (),
391+ }
392+
393+ last_char_idx = char_idx;
394+ last_row = grapheme_pos.row;
395+ char_idx += grapheme.doc_chars();
45 }
396 }
397+
398+ (char_idx, 0)
46}
399}
47
400
48#[cfg(test)]
401#[cfg(test)]
49mod test {
402mod test {
50 use super::*;
403 use super::*;
404+ use crate::Rope;
51
405
52 #[test]
406 #[test]
53 fn test_ordering() {
407 fn test_ordering() {
54 // (0, 5) is less than (1, 0)
408 // (0, 5) is less than (1, 0)
55 assert!(Position::new(0, 5) < Position::new(1, 0));
409 assert!(Position::new(0, 5) < Position::new(1, 0));
56 }
410 }
411+
412+ #[test]
413+ fn test_coords_at_pos() {
414+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
415+ let slice = text.slice(..);
416+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
417+ assert_eq!(coords_at_pos(slice, 5), (0, 5).into()); // position on \n
418+ assert_eq!(coords_at_pos(slice, 6), (1, 0).into()); // position on w
419+ assert_eq!(coords_at_pos(slice, 7), (1, 1).into()); // position on o
420+ assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d
421+
422+ // Test with wide characters.
423+ let text = Rope::from("今日はいい\n");
424+ let slice = text.slice(..);
425+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
426+ assert_eq!(coords_at_pos(slice, 1), (0, 1).into());
427+ assert_eq!(coords_at_pos(slice, 2), (0, 2).into());
428+ assert_eq!(coords_at_pos(slice, 3), (0, 3).into());
429+ assert_eq!(coords_at_pos(slice, 4), (0, 4).into());
430+ assert_eq!(coords_at_pos(slice, 5), (0, 5).into());
431+ assert_eq!(coords_at_pos(slice, 6), (1, 0).into());
432+
433+ // Test with grapheme clusters.
434+ let text = Rope::from("a̐éö̲\r\n");
435+ let slice = text.slice(..);
436+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
437+ assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
438+ assert_eq!(coords_at_pos(slice, 4), (0, 2).into());
439+ assert_eq!(coords_at_pos(slice, 7), (0, 3).into());
440+ assert_eq!(coords_at_pos(slice, 9), (1, 0).into());
441+
442+ // Test with wide-character grapheme clusters.
443+ let text = Rope::from("किमपि\n");
444+ let slice = text.slice(..);
445+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
446+ assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
447+ assert_eq!(coords_at_pos(slice, 3), (0, 2).into());
448+ assert_eq!(coords_at_pos(slice, 5), (0, 3).into());
449+ assert_eq!(coords_at_pos(slice, 6), (1, 0).into());
450+
451+ // Test with tabs.
452+ let text = Rope::from("\tHello\n");
453+ let slice = text.slice(..);
454+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
455+ assert_eq!(coords_at_pos(slice, 1), (0, 1).into());
456+ assert_eq!(coords_at_pos(slice, 2), (0, 2).into());
457+ }
458+
459+ #[test]
460+ #[allow(deprecated)]
461+ fn test_visual_coords_at_pos() {
462+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
463+ let slice = text.slice(..);
464+ assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
465+ assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); // position on \n
466+ assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); // position on w
467+ assert_eq!(visual_coords_at_pos(slice, 7, 8), (1, 1).into()); // position on o
468+ assert_eq!(visual_coords_at_pos(slice, 10, 8), (1, 4).into()); // position on d
469+
470+ // Test with wide characters.
471+ let text = Rope::from("今日はいい\n");
472+ let slice = text.slice(..);
473+ assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
474+ assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 2).into());
475+ assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 4).into());
476+ assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 6).into());
477+ assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 8).into());
478+ assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 10).into());
479+ assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into());
480+
481+ // Test with grapheme clusters.
482+ let text = Rope::from("a̐éö̲\r\n");
483+ let slice = text.slice(..);
484+ assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
485+ assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 1).into());
486+ assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 2).into());
487+ assert_eq!(visual_coords_at_pos(slice, 7, 8), (0, 3).into());
488+ assert_eq!(visual_coords_at_pos(slice, 9, 8), (1, 0).into());
489+
490+ // Test with wide-character grapheme clusters.
491+ // TODO: account for cluster.
492+ let text = Rope::from("किमपि\n");
493+ let slice = text.slice(..);
494+ assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
495+ assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 2).into());
496+ assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 3).into());
497+ assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into());
498+ assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into());
499+
500+ // Test with tabs.
501+ let text = Rope::from("\tHello\n");
502+ let slice = text.slice(..);
503+ assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
504+ assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 8).into());
505+ assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 9).into());
506+ }
507+
508+ #[test]
509+ fn test_visual_off_from_block() {
510+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
511+ let slice = text.slice(..);
512+ let annot = TextAnnotations::default();
513+ let text_fmt = TextFormat::default();
514+ assert_eq!(
515+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
516+ (0, 0).into()
517+ );
518+ assert_eq!(
519+ visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0,
520+ (0, 5).into()
521+ ); // position on \n
522+ assert_eq!(
523+ visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0,
524+ (1, 0).into()
525+ ); // position on w
526+ assert_eq!(
527+ visual_offset_from_block(slice, 0, 7, &text_fmt, &annot).0,
528+ (1, 1).into()
529+ ); // position on o
530+ assert_eq!(
531+ visual_offset_from_block(slice, 0, 10, &text_fmt, &annot).0,
532+ (1, 4).into()
533+ ); // position on d
534+
535+ // Test with wide characters.
536+ let text = Rope::from("今日はいい\n");
537+ let slice = text.slice(..);
538+ assert_eq!(
539+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
540+ (0, 0).into()
541+ );
542+ assert_eq!(
543+ visual_offset_from_block(slice, 0, 1, &text_fmt, &annot).0,
544+ (0, 2).into()
545+ );
546+ assert_eq!(
547+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
548+ (0, 4).into()
549+ );
550+ assert_eq!(
551+ visual_offset_from_block(slice, 0, 3, &text_fmt, &annot).0,
552+ (0, 6).into()
553+ );
554+ assert_eq!(
555+ visual_offset_from_block(slice, 0, 4, &text_fmt, &annot).0,
556+ (0, 8).into()
557+ );
558+ assert_eq!(
559+ visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0,
560+ (0, 10).into()
561+ );
562+ assert_eq!(
563+ visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0,
564+ (1, 0).into()
565+ );
566+
567+ // Test with grapheme clusters.
568+ let text = Rope::from("a̐éö̲\r\n");
569+ let slice = text.slice(..);
570+ assert_eq!(
571+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
572+ (0, 0).into()
573+ );
574+ assert_eq!(
575+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
576+ (0, 1).into()
577+ );
578+ assert_eq!(
579+ visual_offset_from_block(slice, 0, 4, &text_fmt, &annot).0,
580+ (0, 2).into()
581+ );
582+ assert_eq!(
583+ visual_offset_from_block(slice, 0, 7, &text_fmt, &annot).0,
584+ (0, 3).into()
585+ );
586+ assert_eq!(
587+ visual_offset_from_block(slice, 0, 9, &text_fmt, &annot).0,
588+ (1, 0).into()
589+ );
590+
591+ // Test with wide-character grapheme clusters.
592+ // TODO: account for cluster.
593+ let text = Rope::from("किमपि\n");
594+ let slice = text.slice(..);
595+ assert_eq!(
596+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
597+ (0, 0).into()
598+ );
599+ assert_eq!(
600+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
601+ (0, 2).into()
602+ );
603+ assert_eq!(
604+ visual_offset_from_block(slice, 0, 3, &text_fmt, &annot).0,
605+ (0, 3).into()
606+ );
607+ assert_eq!(
608+ visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0,
609+ (0, 5).into()
610+ );
611+ assert_eq!(
612+ visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0,
613+ (1, 0).into()
614+ );
615+
616+ // Test with tabs.
617+ let text = Rope::from("\tHello\n");
618+ let slice = text.slice(..);
619+ assert_eq!(
620+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
621+ (0, 0).into()
622+ );
623+ assert_eq!(
624+ visual_offset_from_block(slice, 0, 1, &text_fmt, &annot).0,
625+ (0, 4).into()
626+ );
627+ assert_eq!(
628+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
629+ (0, 5).into()
630+ );
631+ }
632+ #[test]
633+ fn test_pos_at_coords() {
634+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
635+ let slice = text.slice(..);
636+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
637+ assert_eq!(pos_at_coords(slice, (0, 5).into(), false), 5); // position on \n
638+ assert_eq!(pos_at_coords(slice, (0, 6).into(), false), 6); // position after \n
639+ assert_eq!(pos_at_coords(slice, (0, 6).into(), true), 5); // position after \n
640+ assert_eq!(pos_at_coords(slice, (1, 0).into(), false), 6); // position on w
641+ assert_eq!(pos_at_coords(slice, (1, 1).into(), false), 7); // position on o
642+ assert_eq!(pos_at_coords(slice, (1, 4).into(), false), 10); // position on d
643+
644+ // Test with wide characters.
645+ // TODO: account for character width.
646+ let text = Rope::from("今日はいい\n");
647+ let slice = text.slice(..);
648+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
649+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 1);
650+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 2);
651+ assert_eq!(pos_at_coords(slice, (0, 3).into(), false), 3);
652+ assert_eq!(pos_at_coords(slice, (0, 4).into(), false), 4);
653+ assert_eq!(pos_at_coords(slice, (0, 5).into(), false), 5);
654+ assert_eq!(pos_at_coords(slice, (0, 6).into(), false), 6);
655+ assert_eq!(pos_at_coords(slice, (0, 6).into(), true), 5);
656+ assert_eq!(pos_at_coords(slice, (1, 0).into(), false), 6);
657+
658+ // Test with grapheme clusters.
659+ let text = Rope::from("a̐éö̲\r\n");
660+ let slice = text.slice(..);
661+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
662+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 2);
663+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 4);
664+ assert_eq!(pos_at_coords(slice, (0, 3).into(), false), 7); // \r\n is one char here
665+ assert_eq!(pos_at_coords(slice, (0, 4).into(), false), 9);
666+ assert_eq!(pos_at_coords(slice, (0, 4).into(), true), 7);
667+ assert_eq!(pos_at_coords(slice, (1, 0).into(), false), 9);
668+
669+ // Test with wide-character grapheme clusters.
670+ // TODO: account for character width.
671+ let text = Rope::from("किमपि");
672+ // 2 - 1 - 2 codepoints
673+ // TODO: delete handling as per https://news.ycombinator.com/item?id=20058454
674+ let slice = text.slice(..);
675+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
676+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 2);
677+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 3);
678+ assert_eq!(pos_at_coords(slice, (0, 3).into(), false), 5);
679+ assert_eq!(pos_at_coords(slice, (0, 3).into(), true), 5);
680+
681+ // Test with tabs.
682+ // Todo: account for tab stops.
683+ let text = Rope::from("\tHello\n");
684+ let slice = text.slice(..);
685+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
686+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 1);
687+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 2);
688+
689+ // Test out of bounds.
690+ let text = Rope::new();
691+ let slice = text.slice(..);
692+ assert_eq!(pos_at_coords(slice, (10, 0).into(), true), 0);
693+ assert_eq!(pos_at_coords(slice, (0, 10).into(), true), 0);
694+ assert_eq!(pos_at_coords(slice, (10, 10).into(), true), 0);
695+ }
696+
697+ #[test]
698+ #[allow(deprecated)]
699+ fn test_pos_at_visual_coords() {
700+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
701+ let slice = text.slice(..);
702+ assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0);
703+ assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 5); // position on \n
704+ assert_eq!(pos_at_visual_coords(slice, (0, 6).into(), 4), 5); // position after \n
705+ assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 6); // position on w
706+ assert_eq!(pos_at_visual_coords(slice, (1, 1).into(), 4), 7); // position on o
707+ assert_eq!(pos_at_visual_coords(slice, (1, 4).into(), 4), 10); // position on d
708+
709+ // Test with wide characters.
710+ let text = Rope::from("今日はいい\n");
711+ let slice = text.slice(..);
712+ assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0);
713+ assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0);
714+ assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 1);
715+ assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 1);
716+ assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 2);
717+ assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 2);
718+ assert_eq!(pos_at_visual_coords(slice, (0, 6).into(), 4), 3);
719+ assert_eq!(pos_at_visual_coords(slice, (0, 7).into(), 4), 3);
720+ assert_eq!(pos_at_visual_coords(slice, (0, 8).into(), 4), 4);
721+ assert_eq!(pos_at_visual_coords(slice, (0, 9).into(), 4), 4);
722+ // assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4, false), 5);
723+ // assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4, true), 5);
724+ assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 6);
725+
726+ // Test with grapheme clusters.
727+ let text = Rope::from("a̐éö̲\r\n");
728+ let slice = text.slice(..);
729+ assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0);
730+ assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 2);
731+ assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 4);
732+ assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 7); // \r\n is one char here
733+ assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 7);
734+ assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 9);
735+
736+ // Test with wide-character grapheme clusters.
737+ let text = Rope::from("किमपि");
738+ // 2 - 1 - 2 codepoints
739+ // TODO: delete handling as per https://news.ycombinator.com/item?id=20058454
740+ let slice = text.slice(..);
741+ assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0);
742+ assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0);
743+ assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 2);
744+ assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 3);
745+
746+ // Test with tabs.
747+ let text = Rope::from("\tHello\n");
748+ let slice = text.slice(..);
749+ assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0);
750+ assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0);
751+ assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 0);
752+ assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 0);
753+ assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 1);
754+ assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 2);
755+
756+ // Test out of bounds.
757+ let text = Rope::new();
758+ let slice = text.slice(..);
759+ assert_eq!(pos_at_visual_coords(slice, (10, 0).into(), 4), 0);
760+ assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4), 0);
761+ assert_eq!(pos_at_visual_coords(slice, (10, 10).into(), 4), 0);
762+ }
763+
764+ #[test]
765+ fn test_char_idx_at_visual_row_offset() {
766+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ\nfoo");
767+ let slice = text.slice(..);
768+ let mut text_fmt = TextFormat::default();
769+ for i in 0isize..3isize {
770+ for j in -2isize..=2isize {
771+ if !(0..3).contains(&(i + j)) {
772+ continue;
773+ }
774+ println!("{i} {j}");
775+ assert_eq!(
776+ char_idx_at_visual_offset(
777+ slice,
778+ slice.line_to_char(i as usize),
779+ j,
780+ 3,
781+ &text_fmt,
782+ &TextAnnotations::default(),
783+ )
784+ .0,
785+ slice.line_to_char((i + j) as usize) + 3
786+ );
787+ }
788+ }
789+
790+ text_fmt.soft_wrap = true;
791+ let mut softwrapped_text = "foo ".repeat(10);
792+ softwrapped_text.push('\n');
793+ let last_char = softwrapped_text.len() - 1;
794+
795+ let text = Rope::from(softwrapped_text.repeat(3));
796+ let slice = text.slice(..);
797+ assert_eq!(
798+ char_idx_at_visual_offset(
799+ slice,
800+ last_char,
801+ 0,
802+ 0,
803+ &text_fmt,
804+ &TextAnnotations::default(),
805+ )
806+ .0,
807+ 32
808+ );
809+ assert_eq!(
810+ char_idx_at_visual_offset(
811+ slice,
812+ last_char,
813+ -1,
814+ 0,
815+ &text_fmt,
816+ &TextAnnotations::default(),
817+ )
818+ .0,
819+ 16
820+ );
821+ assert_eq!(
822+ char_idx_at_visual_offset(
823+ slice,
824+ last_char,
825+ -2,
826+ 0,
827+ &text_fmt,
828+ &TextAnnotations::default(),
829+ )
830+ .0,
831+ 0
832+ );
833+ assert_eq!(
834+ char_idx_at_visual_offset(
835+ slice,
836+ softwrapped_text.len() + last_char,
837+ -2,
838+ 0,
839+ &text_fmt,
840+ &TextAnnotations::default(),
841+ )
842+ .0,
843+ softwrapped_text.len()
844+ );
845+
846+ assert_eq!(
847+ char_idx_at_visual_offset(
848+ slice,
849+ softwrapped_text.len() + last_char,
850+ -5,
851+ 0,
852+ &text_fmt,
853+ &TextAnnotations::default(),
854+ )
855+ .0,
856+ 0
857+ );
858+ }
57}
859}
helix-core/src/register.rsdeleted−21
@@ -1,21 +0,0 @@
1−use crate::Tendril;
2−use once_cell::sync::Lazy;
3−use std::{collections::HashMap, sync::RwLock};
4−
5−// TODO: could be an instance on Editor
6−static REGISTRY: Lazy<RwLock<HashMap<char, Vec<String>>>> =
7− Lazy::new(|| RwLock::new(HashMap::new()));
8−
9−pub fn get(register: char) -> Option<Vec<String>> {
10− let registry = REGISTRY.read().unwrap();
11−
12− // TODO: no cloning
13− registry.get(&register).cloned()
14−}
15−
16−// restoring: bool
17−pub fn set(register: char, values: Vec<String>) {
18− let mut registry = REGISTRY.write().unwrap();
19−
20− registry.insert(register, values);
21−}
helix-core/src/rope_reader.rsnew file+37
@@ -0,0 +1,37 @@
1+use std::io;
2+
3+use ropey::iter::Chunks;
4+use ropey::RopeSlice;
5+
6+pub struct RopeReader<'a> {
7+ current_chunk: &'a [u8],
8+ chunks: Chunks<'a>,
9+}
10+
11+impl<'a> RopeReader<'a> {
12+ pub fn new(rope: RopeSlice<'a>) -> RopeReader<'a> {
13+ RopeReader {
14+ current_chunk: &[],
15+ chunks: rope.chunks(),
16+ }
17+ }
18+}
19+
20+impl io::Read for RopeReader<'_> {
21+ fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
22+ let buf_len = buf.len();
23+ loop {
24+ let read_bytes = self.current_chunk.read(buf)?;
25+ buf = &mut buf[read_bytes..];
26+ if buf.is_empty() {
27+ return Ok(buf_len);
28+ }
29+
30+ if let Some(next_chunk) = self.chunks.next() {
31+ self.current_chunk = next_chunk.as_bytes();
32+ } else {
33+ return Ok(buf_len - buf.len());
34+ }
35+ }
36+ }
37+}
helix-core/src/search.rsnew file+67
@@ -0,0 +1,67 @@
1+use crate::RopeSlice;
2+
3+// TODO: switch to std::str::Pattern when it is stable.
4+pub trait CharMatcher {
5+ fn char_match(&self, ch: char) -> bool;
6+}
7+
8+impl CharMatcher for char {
9+ fn char_match(&self, ch: char) -> bool {
10+ *self == ch
11+ }
12+}
13+
14+impl<F: Fn(&char) -> bool> CharMatcher for F {
15+ fn char_match(&self, ch: char) -> bool {
16+ (*self)(&ch)
17+ }
18+}
19+
20+pub fn find_nth_next<M: CharMatcher>(
21+ text: RopeSlice,
22+ char_matcher: M,
23+ mut pos: usize,
24+ n: usize,
25+) -> Option<usize> {
26+ if pos >= text.len_chars() || n == 0 {
27+ return None;
28+ }
29+
30+ let mut chars = text.chars_at(pos);
31+
32+ for _ in 0..n {
33+ loop {
34+ let c = chars.next()?;
35+
36+ pos += 1;
37+
38+ if char_matcher.char_match(c) {
39+ break;
40+ }
41+ }
42+ }
43+
44+ Some(pos - 1)
45+}
46+
47+pub fn find_nth_prev(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Option<usize> {
48+ if pos == 0 || n == 0 {
49+ return None;
50+ }
51+
52+ let mut chars = text.chars_at(pos);
53+
54+ for _ in 0..n {
55+ loop {
56+ let c = chars.prev()?;
57+
58+ pos -= 1;
59+
60+ if c == ch {
61+ break;
62+ }
63+ }
64+ }
65+
66+ Some(pos)
67+}
helix-core/src/selection.rs+1043−148

This diff is too big to show here.

helix-core/src/shellwords.rsnew file+350

This diff is too big to show here.

helix-core/src/state.rsdeleted−354

This diff is too big to show here.

helix-core/src/surround.rsnew file+407

This diff is too big to show here.

helix-core/src/syntax.rs+2172−949

This diff is too big to show here.

helix-core/src/test.rsnew file+386

This diff is too big to show here.

helix-core/src/text_annotations.rsnew file+271

This diff is too big to show here.

helix-core/src/textobject.rsnew file+588

This diff is too big to show here.

helix-core/src/transaction.rs+618−163

This diff is too big to show here.

helix-core/src/wrap.rsnew file+9

This diff is too big to show here.

helix-core/tests/data/indent/cpp.cppnew file+48

This diff is too big to show here.

helix-core/tests/data/indent/languages.tomlnew file+26

This diff is too big to show here.

helix-core/tests/data/indent/rust.rsnew file+105

This diff is too big to show here.

helix-core/tests/indent.rsnew file+232

This diff is too big to show here.

helix-dap/Cargo.tomlnew file+27

This diff is too big to show here.

helix-dap/src/client.rsnew file+519

This diff is too big to show here.

helix-dap/src/lib.rsnew file+24

This diff is too big to show here.

helix-dap/src/transport.rsnew file+290

This diff is too big to show here.

helix-dap/src/types.rsnew file+890

This diff is too big to show here.

helix-event/Cargo.tomlnew file+16

This diff is too big to show here.

helix-event/src/lib.rsnew file+8

This diff is too big to show here.

helix-event/src/redraw.rsnew file+49

This diff is too big to show here.

helix-loader/Cargo.tomlnew file+36

This diff is too big to show here.

helix-loader/build.rsnew file+77

This diff is too big to show here.

helix-loader/src/config.rsnew file+46

This diff is too big to show here.

helix-loader/src/grammar.rsnew file+599

This diff is too big to show here.

helix-loader/src/lib.rsnew file+362

This diff is too big to show here.

helix-loader/src/main.rsnew file+9

This diff is too big to show here.

helix-lsp/Cargo.toml+23−17

This diff is too big to show here.

helix-lsp/src/client.rs+1374−186

This diff is too big to show here.

helix-lsp/src/file_event.rsnew file+193

This diff is too big to show here.

helix-lsp/src/jsonrpc.rsnew file+411

This diff is too big to show here.

helix-lsp/src/lib.rs+1000−61

This diff is too big to show here.

helix-lsp/src/snippet.rsnew file+1010

This diff is too big to show here.

helix-lsp/src/transport.rs+335−121

This diff is too big to show here.

helix-parsec/Cargo.tomlnew file+14

This diff is too big to show here.

helix-parsec/src/lib.rsnew file+574

This diff is too big to show here.

helix-syntax/Cargo.tomldeleted−13

This diff is too big to show here.

helix-syntax/build.rsdeleted−123

This diff is too big to show here.

helix-syntax/languages/tree-sitter-agdadeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-bashdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-cdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-c-sharpdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-cppdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-cssdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-godeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-haskelldeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-htmldeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-javadeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-javascriptdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-jsondeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-juliadeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-phpdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-pythondeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-rubydeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-rustdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-scaladeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-swiftdeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-tomldeleted−1

This diff is too big to show here.

helix-syntax/languages/tree-sitter-typescriptdeleted−1

This diff is too big to show here.

helix-syntax/src/lib.rsdeleted−89

This diff is too big to show here.

helix-term/Cargo.toml+68−14

This diff is too big to show here.

helix-term/README.mddeleted−7

This diff is too big to show here.

helix-term/build.rsnew file+9

This diff is too big to show here.

helix-term/src/application.rs+1132−608

This diff is too big to show here.

helix-term/src/args.rsnew file+150

This diff is too big to show here.

helix-term/src/commands.rsnew file+5841

This diff is too big to show here.

helix-term/src/commands/dap.rsnew file+769

This diff is too big to show here.

helix-term/src/commands/lsp.rsnew file+1665

This diff is too big to show here.

helix-term/src/commands/typed.rsnew file+3226

This diff is too big to show here.

helix-term/src/component.rsdeleted−20

This diff is too big to show here.

helix-term/src/compositor.rs+249−81

This diff is too big to show here.

helix-term/src/config.rsnew file+187

This diff is too big to show here.

helix-term/src/health.rsnew file+372

This diff is too big to show here.

helix-term/src/job.rsnew file+140

This diff is too big to show here.

helix-term/src/keymap.rsnew file+595

This diff is too big to show here.

helix-term/src/keymap/default.rsnew file+393

This diff is too big to show here.

helix-term/src/keymap/macros.rsnew file+126

This diff is too big to show here.

helix-term/src/lib.rsnew file+49

This diff is too big to show here.

helix-term/src/main.rs+133−49

This diff is too big to show here.

helix-term/src/ui/completion.rsnew file+590

This diff is too big to show here.

helix-term/src/ui/document.rsnew file+520

This diff is too big to show here.

helix-term/src/ui/editor.rsnew file+1551

This diff is too big to show here.

helix-term/src/ui/info.rsnew file+38

This diff is too big to show here.

helix-term/src/ui/lsp.rsnew file+139

This diff is too big to show here.

helix-term/src/ui/markdown.rsnew file+362

This diff is too big to show here.

helix-term/src/ui/menu.rsnew file+421

This diff is too big to show here.

helix-term/src/ui/mod.rsnew file+495

This diff is too big to show here.

helix-term/src/ui/overlay.rsnew file+76

This diff is too big to show here.

helix-term/src/ui/picker.rsnew file+1010

This diff is too big to show here.

helix-term/src/ui/popup.rsnew file+316

This diff is too big to show here.

helix-term/src/ui/prompt.rsnew file+673

This diff is too big to show here.

helix-term/src/ui/spinner.rsnew file+74

This diff is too big to show here.

helix-term/src/ui/statusline.rsnew file+515

This diff is too big to show here.

helix-term/src/ui/text.rsnew file+67

This diff is too big to show here.

helix-term/test.rsdeleted−36

This diff is too big to show here.

helix-term/test.txtdeleted−1000

This diff is too big to show here.

helix-term/tests/integration.rsnew file+25

This diff is too big to show here.

helix-term/tests/test/auto_indent.rsnew file+21

This diff is too big to show here.

helix-term/tests/test/auto_pairs.rsnew file+541

This diff is too big to show here.

helix-term/tests/test/commands.rsnew file+528

This diff is too big to show here.

helix-term/tests/test/commands/movement.rsnew file+452

This diff is too big to show here.

helix-term/tests/test/commands/write.rsnew file+566

This diff is too big to show here.

helix-term/tests/test/helpers.rsnew file+370

This diff is too big to show here.

helix-term/tests/test/languages/go.rsnew file+41

This diff is too big to show here.

helix-term/tests/test/languages/mod.rsnew file+4

This diff is too big to show here.

helix-term/tests/test/languages/yaml.rsnew file+819

This diff is too big to show here.

helix-term/tests/test/movement.rsnew file+554

This diff is too big to show here.

helix-term/tests/test/prompt.rsnew file+16

This diff is too big to show here.

helix-term/tests/test/splits.rsnew file+192

This diff is too big to show here.

helix-tui/.gitignorenew file+6

This diff is too big to show here.

helix-tui/Cargo.tomlnew file+28

This diff is too big to show here.

helix-tui/README.mdnew file+6

This diff is too big to show here.

helix-tui/src/backend/crossterm.rsnew file+434

This diff is too big to show here.

helix-tui/src/backend/mod.rsnew file+30

This diff is too big to show here.

helix-tui/src/backend/test.rsnew file+167

This diff is too big to show here.

helix-tui/src/buffer.rsnew file+943

This diff is too big to show here.

helix-tui/src/layout.rsnew file+371

This diff is too big to show here.

helix-tui/src/lib.rsnew file+139

This diff is too big to show here.

helix-tui/src/symbols.rsnew file+233

This diff is too big to show here.

helix-tui/src/terminal.rsnew file+237

This diff is too big to show here.

helix-tui/src/text.rsnew file+480

This diff is too big to show here.

helix-tui/src/widgets/block.rsnew file+491

This diff is too big to show here.

helix-tui/src/widgets/list.rsnew file+249

This diff is too big to show here.

helix-tui/src/widgets/mod.rsnew file+50

This diff is too big to show here.

helix-tui/src/widgets/paragraph.rsnew file+197

This diff is too big to show here.

helix-tui/src/widgets/reflow.rsnew file+535

This diff is too big to show here.

helix-tui/src/widgets/table.rsnew file+505

This diff is too big to show here.

helix-tui/tests/terminal.rsnew file+33

This diff is too big to show here.

helix-tui/tests/widgets_block.rsnew file+213

This diff is too big to show here.

helix-tui/tests/widgets_list.rsnew file+88

This diff is too big to show here.

helix-tui/tests/widgets_paragraph.rsnew file+220

This diff is too big to show here.

helix-tui/tests/widgets_table.rsnew file+717

This diff is too big to show here.

helix-vcs/Cargo.tomlnew file+32

This diff is too big to show here.

helix-vcs/src/diff.rsnew file+292

This diff is too big to show here.

helix-vcs/src/diff/line_cache.rsnew file+138

This diff is too big to show here.

helix-vcs/src/diff/worker.rsnew file+204

This diff is too big to show here.

helix-vcs/src/diff/worker/test.rsnew file+145

This diff is too big to show here.

helix-vcs/src/git.rsnew file+139

This diff is too big to show here.

helix-vcs/src/git/test.rsnew file+121

This diff is too big to show here.

helix-vcs/src/lib.rsnew file+78

This diff is too big to show here.

helix-view/Cargo.toml+50−14

This diff is too big to show here.

helix-view/src/base64.rsnew file+163

This diff is too big to show here.

helix-view/src/clipboard.rsnew file+413

This diff is too big to show here.

helix-view/src/commands.rsdeleted−680

This diff is too big to show here.

helix-view/src/document.rs+2029−104

This diff is too big to show here.

helix-view/src/editor.rs+1868−26

This diff is too big to show here.

helix-view/src/env.rsnew file+8

This diff is too big to show here.

helix-view/src/graphics.rsnew file+702

This diff is too big to show here.

helix-view/src/gutter.rsnew file+435

This diff is too big to show here.

helix-view/src/handlers/dap.rsnew file+394

This diff is too big to show here.

helix-view/src/handlers/lsp.rsnew file+1

This diff is too big to show here.

helix-view/src/info.rsnew file+68

This diff is too big to show here.

helix-view/src/input.rsnew file+882

This diff is too big to show here.

helix-view/src/keyboard.rsnew file+346

This diff is too big to show here.

helix-view/src/keymap.rsdeleted−217

This diff is too big to show here.

helix-view/src/lib.rs+71−3

This diff is too big to show here.

helix-view/src/macros.rsnew file+75

This diff is too big to show here.

helix-view/src/prompt.rsdeleted−126

This diff is too big to show here.

helix-view/src/register.rsnew file+333

This diff is too big to show here.

helix-view/src/theme.rs+558−163

This diff is too big to show here.

helix-view/src/tree.rsnew file+876

This diff is too big to show here.

helix-view/src/view.rs+970−54

This diff is too big to show here.

helix-view/tests/encoding/big5_in.txtnew file+19787

This diff is too big to show here.

helix-view/tests/encoding/big5_in_ref.txtnew file+19787

This diff is too big to show here.

helix-view/tests/encoding/big5_out.txtnew file+14601

This diff is too big to show here.

helix-view/tests/encoding/big5_out_ref.txtnew file+14601

This diff is too big to show here.

helix-view/tests/encoding/euc_kr_in.txtnew file+23945

This diff is too big to show here.

helix-view/tests/encoding/euc_kr_in_ref.txtnew file+23945

This diff is too big to show here.

helix-view/tests/encoding/euc_kr_out.txtnew file+17053

This diff is too big to show here.

helix-view/tests/encoding/euc_kr_out_ref.txtnew file+17053

This diff is too big to show here.

helix-view/tests/encoding/gb18030_in.txtnew file+23945

This diff is too big to show here.

helix-view/tests/encoding/gb18030_in_ref.txtnew file+23945

This diff is too big to show here.

helix-view/tests/encoding/gb18030_out.txtnew file+23944

This diff is too big to show here.

helix-view/tests/encoding/gb18030_out_ref.txtnew file+23944

This diff is too big to show here.

helix-view/tests/encoding/iso_2022_jp_in.txtnew file+8841

This diff is too big to show here.

helix-view/tests/encoding/iso_2022_jp_in_ref.txtnew file+8841

This diff is too big to show here.

helix-view/tests/encoding/iso_2022_jp_out.txtnew file+7404

This diff is too big to show here.

helix-view/tests/encoding/iso_2022_jp_out_ref.txtnew file+7404

This diff is too big to show here.

helix-view/tests/encoding/jis0208_in.txtnew file+8841

This diff is too big to show here.

helix-view/tests/encoding/jis0208_in_ref.txtnew file+8841

This diff is too big to show here.

helix-view/tests/encoding/jis0208_out.txtnew file+7341

This diff is too big to show here.

helix-view/tests/encoding/jis0208_out_ref.txtnew file+7341

This diff is too big to show here.

helix-view/tests/encoding/jis0212_in.txtnew file+8841

This diff is too big to show here.

helix-view/tests/encoding/jis0212_in_ref.txtnew file+8841

This diff is too big to show here.

helix-view/tests/encoding/shift_jis_in.txtnew file+11285

This diff is too big to show here.

helix-view/tests/encoding/shift_jis_in_ref.txtnew file+11285

This diff is too big to show here.

helix-view/tests/encoding/shift_jis_out.txtnew file+7355

This diff is too big to show here.

helix-view/tests/encoding/shift_jis_out_ref.txtnew file+7355

This diff is too big to show here.

languages.tomlnew file+3008

This diff is too big to show here.

logo.svgnew file+1

This diff is too big to show here.

logo_dark.svgnew file+115

This diff is too big to show here.

logo_light.svgnew file+115

This diff is too big to show here.

runtime/grammars/.gitkeepnew file

No changes to the content.

runtime/queries/_javascript/highlights.scmnew file+36

This diff is too big to show here.

runtime/queries/_javascript/locals.scmnew file+14

This diff is too big to show here.

runtime/queries/_javascript/tags.scmnew file+88

This diff is too big to show here.

runtime/queries/_jsx/highlights.scmnew file+43

This diff is too big to show here.

runtime/queries/_jsx/indents.scmnew file+6

This diff is too big to show here.

runtime/queries/_typescript/highlights.scmnew file+142

This diff is too big to show here.

runtime/queries/_typescript/indents.scmnew file+5

This diff is too big to show here.

runtime/queries/_typescript/locals.scmnew file+33

This diff is too big to show here.

runtime/queries/_typescript/tags.scmnew file+23

This diff is too big to show here.

runtime/queries/_typescript/textobjects.scmnew file+6

This diff is too big to show here.

runtime/queries/agda/highlights.scmnew file+124

This diff is too big to show here.

runtime/queries/astro/highlights.scmnew file+3

This diff is too big to show here.

runtime/queries/astro/injections.scmnew file+9

This diff is too big to show here.

runtime/queries/awk/highlights.scmnew file+122

This diff is too big to show here.

runtime/queries/awk/injections.scmnew file+5

This diff is too big to show here.

runtime/queries/awk/textobjects.scmnew file+10

This diff is too big to show here.

runtime/queries/bash/highlights.scmnew file+55

This diff is too big to show here.

runtime/queries/bash/indents.scmnew file+12

This diff is too big to show here.

runtime/queries/bash/injections.scmnew file+8

This diff is too big to show here.

runtime/queries/bash/textobjects.scmnew file+9

This diff is too big to show here.

runtime/queries/bass/highlights.scmnew file+101

This diff is too big to show here.

runtime/queries/beancount/folds.scmnew file+4

This diff is too big to show here.

runtime/queries/beancount/highlights.scmnew file+49

This diff is too big to show here.

runtime/queries/bibtex/highlights.scmnew file+47

This diff is too big to show here.

runtime/queries/bibtex/locals.scmnew file

No changes to the content.

runtime/queries/bibtex/tags.scmnew file

No changes to the content.

runtime/queries/bicep/highlights.scmnew file+73

This diff is too big to show here.

runtime/queries/blueprint/highlights.scmnew file+61

This diff is too big to show here.

runtime/queries/c-sharp/highlights.scmnew file+378

This diff is too big to show here.

runtime/queries/c-sharp/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/c-sharp/textobjects.scmnew file+21

This diff is too big to show here.

runtime/queries/c/highlights.scmnew file+142

This diff is too big to show here.

runtime/queries/c/indents.scmnew file+51

This diff is too big to show here.

runtime/queries/c/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/c/textobjects.scmnew file+21

This diff is too big to show here.

runtime/queries/cairo/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/cairo/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/cairo/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/cairo/locals.scmnew file+1

This diff is too big to show here.

runtime/queries/cairo/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/capnp/folds.scmnew file+14

This diff is too big to show here.

runtime/queries/capnp/highlights.scmnew file+141

This diff is too big to show here.

runtime/queries/capnp/indents.scmnew file+19

This diff is too big to show here.

runtime/queries/capnp/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/capnp/locals.scmnew file+96

This diff is too big to show here.

runtime/queries/clojure/highlights.scmnew file+84

This diff is too big to show here.

runtime/queries/clojure/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/cmake/highlights.scmnew file+97

This diff is too big to show here.

runtime/queries/cmake/indents.scmnew file+10

This diff is too big to show here.

runtime/queries/cmake/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/cmake/textobjects.scmnew file+15

This diff is too big to show here.

runtime/queries/comment/highlights.scmnew file+46

This diff is too big to show here.

runtime/queries/common-lisp/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/common-lisp/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/common-lisp/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/cpon/highlights.scmnew file+26

This diff is too big to show here.

runtime/queries/cpon/indents.scmnew file+12

This diff is too big to show here.

runtime/queries/cpp/highlights.scmnew file+129

This diff is too big to show here.

runtime/queries/cpp/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/cpp/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/cpp/textobjects.scmnew file+7

This diff is too big to show here.

runtime/queries/crystal/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/crystal/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/crystal/locals.scmnew file+1

This diff is too big to show here.

runtime/queries/crystal/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/css/highlights.scmnew file+85

This diff is too big to show here.

runtime/queries/css/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/cue/highlights.scmnew file+110

This diff is too big to show here.

runtime/queries/d/highlights.scmnew file+231

This diff is too big to show here.

runtime/queries/d/indents.scmnew file+17

This diff is too big to show here.

runtime/queries/d/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/d/textobjects.scmnew file+9

This diff is too big to show here.

runtime/queries/dart/highlights.scmnew file+239

This diff is too big to show here.

runtime/queries/dart/indents.scmnew file+100

This diff is too big to show here.

runtime/queries/dart/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/dart/locals.scmnew file+20

This diff is too big to show here.

runtime/queries/dbml/highlights.scmnew file+44

This diff is too big to show here.

runtime/queries/devicetree/highlights.scmnew file+66

This diff is too big to show here.

runtime/queries/dhall/highlights.scmnew file+110

This diff is too big to show here.

runtime/queries/dhall/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/dhall/textobjects.scmnew file+23

This diff is too big to show here.

runtime/queries/diff/highlights.scmnew file+6

This diff is too big to show here.

runtime/queries/dockerfile/highlights.scmnew file+54

This diff is too big to show here.

runtime/queries/dockerfile/injections.scmnew file+6

This diff is too big to show here.

runtime/queries/dot/highlights.scmnew file+43

This diff is too big to show here.

runtime/queries/dot/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/dtd/highlights.scmnew file+39

This diff is too big to show here.

runtime/queries/dtd/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/ecma/README.mdnew file+14

This diff is too big to show here.

runtime/queries/ecma/highlights.scmnew file+249

This diff is too big to show here.

runtime/queries/ecma/indents.scmnew file+28

This diff is too big to show here.

runtime/queries/ecma/injections.scmnew file+36

This diff is too big to show here.

runtime/queries/ecma/locals.scmnew file+43

This diff is too big to show here.

runtime/queries/ecma/textobjects.scmnew file+36

This diff is too big to show here.

runtime/queries/edoc/highlights.scmnew file+52

This diff is too big to show here.

runtime/queries/edoc/injections.scmnew file+20

This diff is too big to show here.

runtime/queries/eex/highlights.scmnew file+6

This diff is too big to show here.

runtime/queries/eex/injections.scmnew file+9

This diff is too big to show here.

runtime/queries/ejs/highlights.scmnew file+12

This diff is too big to show here.

runtime/queries/ejs/injections.scmnew file+7

This diff is too big to show here.

runtime/queries/elixir/highlights.scmnew file+219

This diff is too big to show here.

runtime/queries/elixir/indents.scmnew file+13

This diff is too big to show here.

runtime/queries/elixir/injections.scmnew file+59

This diff is too big to show here.

runtime/queries/elixir/textobjects.scmnew file+35

This diff is too big to show here.

runtime/queries/elm/highlights.scmnew file+83

This diff is too big to show here.

runtime/queries/elm/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/elm/locals.scmnew file+14

This diff is too big to show here.

runtime/queries/elm/tags.scmnew file+19

This diff is too big to show here.

runtime/queries/elm/textobjects.scmnew file+63

This diff is too big to show here.

runtime/queries/elvish/highlights.scmnew file+76

This diff is too big to show here.

runtime/queries/elvish/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/env/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/erb/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/erb/injections.scmnew file+7

This diff is too big to show here.

runtime/queries/erlang/highlights.scmnew file+158

This diff is too big to show here.

runtime/queries/erlang/injections.scmnew file+7

This diff is too big to show here.

runtime/queries/erlang/locals.scmnew file+30

This diff is too big to show here.

runtime/queries/erlang/textobjects.scmnew file+16

This diff is too big to show here.

runtime/queries/esdl/highlights.scmnew file+74

This diff is too big to show here.

runtime/queries/fish/highlights.scmnew file+156

This diff is too big to show here.

runtime/queries/fish/indents.scmnew file+12

This diff is too big to show here.

runtime/queries/fish/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/fish/textobjects.scmnew file+5

This diff is too big to show here.

runtime/queries/forth/highlights.scmnew file+7

This diff is too big to show here.

runtime/queries/fortran/folds.scmnew file+11

This diff is too big to show here.

runtime/queries/fortran/highlights.scmnew file+171

This diff is too big to show here.

runtime/queries/fortran/indents.scmnew file+27

This diff is too big to show here.

runtime/queries/fortran/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/fsharp/highlights.scmnew file+196

This diff is too big to show here.

runtime/queries/fsharp/locals.scmnew file+25

This diff is too big to show here.

runtime/queries/gas/highlights.scmnew file+21

This diff is too big to show here.

runtime/queries/gas/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/gas/textobjects.scmnew file+2

This diff is too big to show here.

runtime/queries/gdscript/highlights.scmnew file+155

This diff is too big to show here.

runtime/queries/gdscript/indents.scmnew file+35

This diff is too big to show here.

runtime/queries/gdscript/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/gdscript/tags.scmnew file+5

This diff is too big to show here.

runtime/queries/gdscript/textobjects.scmnew file+17

This diff is too big to show here.

runtime/queries/gemini/highlights.scmnew file+26

This diff is too big to show here.

runtime/queries/git-attributes/highlights.scmnew file+9

This diff is too big to show here.

runtime/queries/git-commit/highlights.scmnew file+18

This diff is too big to show here.

runtime/queries/git-commit/injections.scmnew file+8

This diff is too big to show here.

runtime/queries/git-commit/textobjects.scmnew file+2

This diff is too big to show here.

runtime/queries/git-config/highlights.scmnew file+27

This diff is too big to show here.

runtime/queries/git-ignore/highlights.scmnew file+25

This diff is too big to show here.

runtime/queries/git-rebase/highlights.scmnew file+36

This diff is too big to show here.

runtime/queries/git-rebase/injections.scmnew file+5

This diff is too big to show here.

runtime/queries/gleam/highlights.scmnew file+120

This diff is too big to show here.

runtime/queries/gleam/locals.scmnew file+15

This diff is too big to show here.

runtime/queries/gleam/textobjects.scmnew file+11

This diff is too big to show here.

runtime/queries/glsl/folds.scmnew file+1

This diff is too big to show here.

runtime/queries/glsl/highlights.scmnew file+37

This diff is too big to show here.

runtime/queries/glsl/indents.scmnew file+19

This diff is too big to show here.

runtime/queries/glsl/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/glsl/locals.scmnew file+1

This diff is too big to show here.

runtime/queries/glsl/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/gn/highlights.scmnew file+42

This diff is too big to show here.

runtime/queries/gn/injections.scmnew file+15

This diff is too big to show here.

runtime/queries/gn/locals.scmnew file+16

This diff is too big to show here.

runtime/queries/go/highlights.scmnew file+200

This diff is too big to show here.

runtime/queries/go/indents.scmnew file+40

This diff is too big to show here.

runtime/queries/go/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/go/locals.scmnew file+38

This diff is too big to show here.

runtime/queries/go/tags.scmnew file+30

This diff is too big to show here.

runtime/queries/go/textobjects.scmnew file+33

This diff is too big to show here.

runtime/queries/godot-resource/highlights.scmnew file+29

This diff is too big to show here.

runtime/queries/godot-resource/injections.scmnew file+18

This diff is too big to show here.

runtime/queries/gomod/highlights.scmnew file+17

This diff is too big to show here.

runtime/queries/gomod/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/gotmpl/highlights.scmnew file+76

This diff is too big to show here.

runtime/queries/gotmpl/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/gowork/highlights.scmnew file+14

This diff is too big to show here.

runtime/queries/gowork/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/graphql/highlights.scmnew file+163

This diff is too big to show here.

runtime/queries/hare/highlights.scmnew file+161

This diff is too big to show here.

runtime/queries/hare/locals.scmnew file+20

This diff is too big to show here.

runtime/queries/haskell-persistent/folds.scmnew file+3

This diff is too big to show here.

runtime/queries/haskell-persistent/highlights.scmnew file+37

This diff is too big to show here.

runtime/queries/haskell-persistent/locals.scmnew file+1

This diff is too big to show here.

runtime/queries/haskell/highlights.scmnew file+138

This diff is too big to show here.

runtime/queries/haskell/injections.scmnew file+14

This diff is too big to show here.

runtime/queries/haskell/locals.scmnew file+4

This diff is too big to show here.

runtime/queries/haskell/textobjects.scmnew file+13

This diff is too big to show here.

runtime/queries/hcl/folds.scmnew file+6

This diff is too big to show here.

runtime/queries/hcl/highlights.scmnew file+100

This diff is too big to show here.

runtime/queries/hcl/indents.scmnew file+13

This diff is too big to show here.

runtime/queries/hcl/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/heex/highlights.scmnew file+63

This diff is too big to show here.

runtime/queries/heex/injections.scmnew file+28

This diff is too big to show here.

runtime/queries/heex/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/hosts/highlights.scmnew file+3

This diff is too big to show here.

runtime/queries/html/highlights.scmnew file+20

This diff is too big to show here.

runtime/queries/html/injections.scmnew file+10

This diff is too big to show here.

runtime/queries/hurl/highlights.scmnew file+127

This diff is too big to show here.

runtime/queries/hurl/indents.scmnew file+11

This diff is too big to show here.

runtime/queries/hurl/injections.scmnew file+14

This diff is too big to show here.

runtime/queries/iex/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/iex/injections.scmnew file+6

This diff is too big to show here.

runtime/queries/ini/highlights.scmnew file+6

This diff is too big to show here.

runtime/queries/janet/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/java/highlights.scmnew file+224

This diff is too big to show here.

runtime/queries/java/indents.scmnew file+35

This diff is too big to show here.

runtime/queries/java/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/java/textobjects.scmnew file+35

This diff is too big to show here.

runtime/queries/javascript/highlights.scmnew file+3

This diff is too big to show here.

runtime/queries/javascript/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/javascript/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/javascript/locals.scmnew file+3

This diff is too big to show here.

runtime/queries/javascript/tags.scmnew file+3

This diff is too big to show here.

runtime/queries/javascript/textobjects.scmnew file+3

This diff is too big to show here.

runtime/queries/jinja/highlights.scmnew file+7

This diff is too big to show here.

runtime/queries/jinja/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/jsdoc/highlights.scmnew file+2

This diff is too big to show here.

runtime/queries/jsdoc/injections.scmnew file+5

This diff is too big to show here.

runtime/queries/json/highlights.scmnew file+20

This diff is too big to show here.

runtime/queries/json/indents.scmnew file+9

This diff is too big to show here.

runtime/queries/json5/highlights.scmnew file+11

This diff is too big to show here.

runtime/queries/jsonnet/highlights.scmnew file+38

This diff is too big to show here.

runtime/queries/jsx/highlights.scmnew file+3

This diff is too big to show here.

runtime/queries/jsx/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/jsx/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/jsx/locals.scmnew file+3

This diff is too big to show here.

runtime/queries/jsx/tags.scmnew file+3

This diff is too big to show here.

runtime/queries/jsx/textobjects.scmnew file+3

This diff is too big to show here.

runtime/queries/julia/folds.scmnew file+11

This diff is too big to show here.

runtime/queries/julia/highlights.scmnew file+294

This diff is too big to show here.

runtime/queries/julia/indents.scmnew file+16

This diff is too big to show here.

runtime/queries/julia/injections.scmnew file+34

This diff is too big to show here.

runtime/queries/julia/locals.scmnew file+44

This diff is too big to show here.

runtime/queries/julia/textobjects.scmnew file+46

This diff is too big to show here.

runtime/queries/just/folds.scmnew file+4

This diff is too big to show here.

runtime/queries/just/highlights.scmnew file+33

This diff is too big to show here.

runtime/queries/just/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/just/injections.scmnew file+16

This diff is too big to show here.

runtime/queries/just/locals.scmnew file+10

This diff is too big to show here.

runtime/queries/just/textobjects.scmnew file+48

This diff is too big to show here.

runtime/queries/kdl/highlights.scmnew file+22

This diff is too big to show here.

runtime/queries/kdl/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/kdl/textobjects.scmnew file+27

This diff is too big to show here.

runtime/queries/kotlin/folds.scmnew file+17

This diff is too big to show here.

runtime/queries/kotlin/highlights.scmnew file+298

This diff is too big to show here.

runtime/queries/kotlin/injections.scmnew file+36

This diff is too big to show here.

runtime/queries/kotlin/locals.scmnew file+15

This diff is too big to show here.

runtime/queries/latex/folds.scmnew file+12

This diff is too big to show here.

runtime/queries/latex/highlights.scmnew file+241

This diff is too big to show here.

runtime/queries/latex/injections.scmnew file+6

This diff is too big to show here.

runtime/queries/latex/textobjects.scmnew file+13

This diff is too big to show here.

runtime/queries/lean/folds.scmnew file+15

This diff is too big to show here.

runtime/queries/lean/highlights.scmnew file+217

This diff is too big to show here.

runtime/queries/lean/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/lean/locals.scmnew file+5

This diff is too big to show here.

runtime/queries/ledger/highlights.scmnew file+38

This diff is too big to show here.

runtime/queries/ledger/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/llvm-mir-yaml/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/llvm-mir-yaml/indents.scmnew file+2

This diff is too big to show here.

runtime/queries/llvm-mir-yaml/injections.scmnew file+9

This diff is too big to show here.

runtime/queries/llvm-mir/highlights.scmnew file+136

This diff is too big to show here.

runtime/queries/llvm-mir/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/llvm-mir/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/llvm-mir/textobjects.scmnew file+12

This diff is too big to show here.

runtime/queries/llvm/highlights.scmnew file+158

This diff is too big to show here.

runtime/queries/llvm/indents.scmnew file+6

This diff is too big to show here.

runtime/queries/llvm/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/llvm/locals.scmnew file+14

This diff is too big to show here.

runtime/queries/llvm/textobjects.scmnew file+20

This diff is too big to show here.

runtime/queries/log/highlights.scmnew file+10

This diff is too big to show here.

runtime/queries/lpf/highlights.scmnew file+19

This diff is too big to show here.

runtime/queries/lua/folds.scmnew file+10

This diff is too big to show here.

runtime/queries/lua/highlights.scmnew file+169

This diff is too big to show here.

runtime/queries/lua/indents.scmnew file+20

This diff is too big to show here.

runtime/queries/lua/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/lua/textobjects.scmnew file+15

This diff is too big to show here.

runtime/queries/make/highlights.scmnew file+175

This diff is too big to show here.

runtime/queries/make/injections.scmnew file+5

This diff is too big to show here.

runtime/queries/markdoc/highlights.scmnew file+16

This diff is too big to show here.

runtime/queries/markdoc/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/markdown.inline/highlights.scmnew file+39

This diff is too big to show here.

runtime/queries/markdown.inline/injections.scmnew file+7

This diff is too big to show here.

runtime/queries/markdown/highlights.scmnew file+62

This diff is too big to show here.

runtime/queries/markdown/injections.scmnew file+22

This diff is too big to show here.

runtime/queries/matlab/context.scmnew file+41

This diff is too big to show here.

runtime/queries/matlab/folds.scmnew file+11

This diff is too big to show here.

runtime/queries/matlab/highlights.scmnew file+128

This diff is too big to show here.

runtime/queries/matlab/indents.scmnew file+23

This diff is too big to show here.

runtime/queries/matlab/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/matlab/locals.scmnew file+19

This diff is too big to show here.

runtime/queries/matlab/textobjects.scmnew file+9

This diff is too big to show here.

runtime/queries/mermaid/highlights.scmnew file+187

This diff is too big to show here.

runtime/queries/meson/highlights.scmnew file+62

This diff is too big to show here.

runtime/queries/meson/indents.scmnew file+17

This diff is too big to show here.

runtime/queries/msbuild/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/msbuild/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/msbuild/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/nasm/highlights.scmnew file+126

This diff is too big to show here.

runtime/queries/nasm/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/nasm/textobjects.scmnew file+15

This diff is too big to show here.

runtime/queries/nickel/highlights.scmnew file+68

This diff is too big to show here.

runtime/queries/nickel/indents.scmnew file+17

This diff is too big to show here.

runtime/queries/nickel/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/nim/highlights.scmnew file+315

This diff is too big to show here.

runtime/queries/nim/indents.scmnew file+54

This diff is too big to show here.

runtime/queries/nim/textobjects.scmnew file+19

This diff is too big to show here.

runtime/queries/nix/highlights.scmnew file+112

This diff is too big to show here.

runtime/queries/nix/injections.scmnew file+178

This diff is too big to show here.

runtime/queries/nu/highlights.scmnew file+287

This diff is too big to show here.

runtime/queries/nu/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/nunjucks/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/nunjucks/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/ocaml-interface/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/ocaml-interface/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/ocaml/highlights.scmnew file+162

This diff is too big to show here.

runtime/queries/ocaml/indents.scmnew file+12

This diff is too big to show here.

runtime/queries/ocaml/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/ocaml/locals.scmnew file+24

This diff is too big to show here.

runtime/queries/odin/highlights.scmnew file+26

This diff is too big to show here.

runtime/queries/odin/indents.scmnew file+16

This diff is too big to show here.

runtime/queries/odin/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/opencl/highlights.scmnew file+150

This diff is too big to show here.

runtime/queries/opencl/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/opencl/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/opencl/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/openscad/highlights.scmnew file+63

This diff is too big to show here.

runtime/queries/org/highlights.scmnew file+24

This diff is too big to show here.

runtime/queries/org/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/pascal/highlights.scmnew file+382

This diff is too big to show here.

runtime/queries/pascal/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/pascal/textobjects.scmnew file+10

This diff is too big to show here.

runtime/queries/passwd/highlights.scmnew file+7

This diff is too big to show here.

runtime/queries/pem/highlights.scmnew file+7

This diff is too big to show here.

runtime/queries/perl/fold.scmnew file+19

This diff is too big to show here.

runtime/queries/perl/highlights.scmnew file+98

This diff is too big to show here.

runtime/queries/perl/indents.scmnew file+29

This diff is too big to show here.

runtime/queries/perl/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/perl/textobjects.scmnew file+14

This diff is too big to show here.

runtime/queries/php/highlights.scmnew file+318

This diff is too big to show here.

runtime/queries/php/indents.scmnew file+17

This diff is too big to show here.

runtime/queries/php/injections.scmnew file+25

This diff is too big to show here.

runtime/queries/php/tags.scmnew file+26

This diff is too big to show here.

runtime/queries/php/textobjects.scmnew file+40

This diff is too big to show here.

runtime/queries/po/highlights.scmnew file+15

This diff is too big to show here.

runtime/queries/po/textobjects.scmnew file+6

This diff is too big to show here.

runtime/queries/pod/highlights.scmnew file+61

This diff is too big to show here.

runtime/queries/ponylang/highlights.scmnew file+176

This diff is too big to show here.

runtime/queries/ponylang/indents.scmnew file+32

This diff is too big to show here.

runtime/queries/ponylang/locals.scmnew file+37

This diff is too big to show here.

runtime/queries/ponylang/textobjects.scmnew file+64

This diff is too big to show here.

runtime/queries/prisma/highlights.scmnew file+58

This diff is too big to show here.

runtime/queries/protobuf/highlights.scmnew file+58

This diff is too big to show here.

runtime/queries/protobuf/indents.scmnew file+11

This diff is too big to show here.

runtime/queries/protobuf/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/prql/highlights.scmnew file+143

This diff is too big to show here.

runtime/queries/prql/injections.scmnew file+8

This diff is too big to show here.

runtime/queries/purescript/highlights.scmnew file+164

This diff is too big to show here.

runtime/queries/purescript/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/purescript/locals.scmnew file+4

This diff is too big to show here.

runtime/queries/purescript/textobjects.scmnew file+13

This diff is too big to show here.

runtime/queries/python/highlights.scmnew file+228

This diff is too big to show here.

runtime/queries/python/indents.scmnew file+84

This diff is too big to show here.

runtime/queries/python/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/python/locals.scmnew file+44

This diff is too big to show here.

runtime/queries/python/tags.scmnew file+12

This diff is too big to show here.

runtime/queries/python/textobjects.scmnew file+23

This diff is too big to show here.

runtime/queries/qml/highlights.scmnew file+90

This diff is too big to show here.

runtime/queries/qml/indents.scmnew file+6

This diff is too big to show here.

runtime/queries/qml/injections.scmnew file+16

This diff is too big to show here.

runtime/queries/r/highlights.scmnew file+128

This diff is too big to show here.

runtime/queries/r/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/r/locals.scmnew file+11

This diff is too big to show here.

runtime/queries/racket/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/racket/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/racket/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/regex/highlights.scmnew file+53

This diff is too big to show here.

runtime/queries/rego/highlights.scmnew file+68

This diff is too big to show here.

runtime/queries/rego/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/rescript/highlights.scmnew file+220

This diff is too big to show here.

runtime/queries/rescript/injections.scmnew file+8

This diff is too big to show here.

runtime/queries/rescript/locals.scmnew file+7

This diff is too big to show here.

runtime/queries/rescript/textobjects.scmnew file+112

This diff is too big to show here.

runtime/queries/rmarkdown/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/rmarkdown/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/rmarkdown/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/robot/highlights.scmnew file+59

This diff is too big to show here.

runtime/queries/ron/highlights.scmnew file+43

This diff is too big to show here.

runtime/queries/ron/indents.scmnew file+12

This diff is too big to show here.

runtime/queries/ron/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/rst/highlights.scmnew file+38

This diff is too big to show here.

runtime/queries/ruby/highlights.scmnew file+190

This diff is too big to show here.

runtime/queries/ruby/indents.scmnew file+24

This diff is too big to show here.

runtime/queries/ruby/injections.scmnew file+8

This diff is too big to show here.

runtime/queries/ruby/locals.scmnew file+27

This diff is too big to show here.

runtime/queries/ruby/tags.scmnew file+64

This diff is too big to show here.

runtime/queries/ruby/textobjects.scmnew file+44

This diff is too big to show here.

runtime/queries/rust/highlights.scmnew file+386

This diff is too big to show here.

runtime/queries/rust/indents.scmnew file+148

This diff is too big to show here.

runtime/queries/rust/injections.scmnew file+59

This diff is too big to show here.

runtime/queries/rust/locals.scmnew file+31

This diff is too big to show here.

runtime/queries/rust/textobjects.scmnew file+58

This diff is too big to show here.

runtime/queries/sage/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/sage/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/sage/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/scala/highlights.scmnew file+275

This diff is too big to show here.

runtime/queries/scala/indents.scmnew file+22

This diff is too big to show here.

runtime/queries/scala/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/scala/locals.scmnew file+29

This diff is too big to show here.

runtime/queries/scheme/highlights.scmnew file+104

This diff is too big to show here.

runtime/queries/scheme/indents.scmnew file+43

This diff is too big to show here.

runtime/queries/scheme/injections.scmnew file+5

This diff is too big to show here.

runtime/queries/scss/highlights.scmnew file+86

This diff is too big to show here.

runtime/queries/scss/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/slint/highlights.scmnew file+171

This diff is too big to show here.

runtime/queries/slint/indents.scmnew file+12

This diff is too big to show here.

runtime/queries/slint/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/slint/locals.scmnew file+3

This diff is too big to show here.

runtime/queries/smali/folds.scmnew file+12

This diff is too big to show here.

runtime/queries/smali/highlights.scmnew file+216

This diff is too big to show here.

runtime/queries/smali/indents.scmnew file+31

This diff is too big to show here.

runtime/queries/smali/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/smali/locals.scmnew file+42

This diff is too big to show here.

runtime/queries/smithy/highlights.scmnew file+102

This diff is too big to show here.

runtime/queries/sml/highlights.scmnew file+91

This diff is too big to show here.

runtime/queries/solidity/highlights.scmnew file+219

This diff is too big to show here.

runtime/queries/solidity/locals.scmnew file+9

This diff is too big to show here.

runtime/queries/sql/highlights.scmnew file+429

This diff is too big to show here.

runtime/queries/sshclientconfig/highlights.scmnew file+324

This diff is too big to show here.

runtime/queries/starlark/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/starlark/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/starlark/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/strace/highlights.scmnew file+8

This diff is too big to show here.

runtime/queries/svelte/highlights.scmnew file+68

This diff is too big to show here.

runtime/queries/svelte/indents.scmnew file+18

This diff is too big to show here.

runtime/queries/svelte/injections.scmnew file+40

This diff is too big to show here.

runtime/queries/sway/highlights.scmnew file+336

This diff is too big to show here.

runtime/queries/sway/indents.scmnew file+71

This diff is too big to show here.

runtime/queries/sway/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/sway/locals.scmnew file+17

This diff is too big to show here.

runtime/queries/sway/textobjects.scmnew file+52

This diff is too big to show here.

runtime/queries/swift/highlights.scmnew file+171

This diff is too big to show here.

runtime/queries/swift/locals.scmnew file+7

This diff is too big to show here.

runtime/queries/t32/highlights.scmnew file+227

This diff is too big to show here.

runtime/queries/t32/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/tablegen/highlights.scmnew file+90

This diff is too big to show here.

runtime/queries/tablegen/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/tablegen/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/tablegen/textobjects.scmnew file+16

This diff is too big to show here.

runtime/queries/task/highlights.scmnew file+4

This diff is too big to show here.

runtime/queries/task/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/templ/highlights.scmnew file+99

This diff is too big to show here.

runtime/queries/templ/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/tfvars/folds.scmnew file+1

This diff is too big to show here.

runtime/queries/tfvars/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/tfvars/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/tfvars/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/todotxt/highlights.scmnew file+6

This diff is too big to show here.

runtime/queries/toml/highlights.scmnew file+48

This diff is too big to show here.

runtime/queries/toml/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/tsq/highlights.scmnew file+50

This diff is too big to show here.

runtime/queries/tsq/injections.scmnew file+8

This diff is too big to show here.

runtime/queries/tsx/highlights.scmnew file+3

This diff is too big to show here.

runtime/queries/tsx/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/tsx/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/tsx/locals.scmnew file+3

This diff is too big to show here.

runtime/queries/tsx/tags.scmnew file+3

This diff is too big to show here.

runtime/queries/tsx/textobjects.scmnew file+3

This diff is too big to show here.

runtime/queries/twig/highlights.scmnew file+60

This diff is too big to show here.

runtime/queries/twig/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/typescript/highlights.scmnew file+3

This diff is too big to show here.

runtime/queries/typescript/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/typescript/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/typescript/locals.scmnew file+3

This diff is too big to show here.

runtime/queries/typescript/tags.scmnew file+3

This diff is too big to show here.

runtime/queries/typescript/textobjects.scmnew file+3

This diff is too big to show here.

runtime/queries/typst/highlights.scmnew file+77

This diff is too big to show here.

runtime/queries/typst/injections.scmnew file+6

This diff is too big to show here.

runtime/queries/ungrammar/highlights.scmnew file+17

This diff is too big to show here.

runtime/queries/unison/highlights.scmnew file+87

This diff is too big to show here.

runtime/queries/unison/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/uxntal/highlights.scmnew file+15

This diff is too big to show here.

runtime/queries/v/highlights.scmnew file+241

This diff is too big to show here.

runtime/queries/v/indents.scmnew file+16

This diff is too big to show here.

runtime/queries/v/injections.scmnew file+6

This diff is too big to show here.

runtime/queries/v/textobjects.scmnew file+27

This diff is too big to show here.

runtime/queries/vala/highlights.scmnew file+218

This diff is too big to show here.

runtime/queries/verilog/highlights.scmnew file+311

This diff is too big to show here.

runtime/queries/verilog/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/verilog/locals.scmnew file+61

This diff is too big to show here.

runtime/queries/verilog/textobjects.scmnew file+6

This diff is too big to show here.

runtime/queries/vhdl/highlights.scmnew file+338

This diff is too big to show here.

runtime/queries/vhs/highlights.scmnew file+36

This diff is too big to show here.

runtime/queries/vue/highlights.scmnew file+21

This diff is too big to show here.

runtime/queries/vue/injections.scmnew file+44

This diff is too big to show here.

runtime/queries/wast/highlights.scmnew file+21

This diff is too big to show here.

runtime/queries/wat/highlights.scmnew file+20

This diff is too big to show here.

runtime/queries/webc/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/webc/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/wgsl/highlights.scmnew file+117

This diff is too big to show here.

runtime/queries/wgsl/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/wit/highlights.scmnew file+67

This diff is too big to show here.

runtime/queries/wit/indents.scmnew file+13

This diff is too big to show here.

runtime/queries/wren/highlights.scmnew file+82

This diff is too big to show here.

runtime/queries/wren/indents.scmnew file+29

This diff is too big to show here.

runtime/queries/wren/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/wren/locals.scmnew file+21

This diff is too big to show here.

runtime/queries/wren/textobjects.scmnew file+16

This diff is too big to show here.

runtime/queries/xit/highlights.scmnew file+11

This diff is too big to show here.

runtime/queries/xml/highlights.scmnew file+42

This diff is too big to show here.

runtime/queries/xml/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/xml/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/yaml/highlights.scmnew file+43

This diff is too big to show here.

runtime/queries/yaml/indents.scmnew file+36

This diff is too big to show here.

runtime/queries/yaml/injections.scmnew file+54

This diff is too big to show here.

runtime/queries/yuck/highlights.scmnew file+107

This diff is too big to show here.

runtime/queries/yuck/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/zig/highlights.scmnew file+231

This diff is too big to show here.

runtime/queries/zig/indents.scmnew file+17

This diff is too big to show here.

runtime/queries/zig/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/zig/textobjects.scmnew file+23

This diff is too big to show here.

runtime/themes/README.mdnew file+17

This diff is too big to show here.

runtime/themes/acme.tomlnew file+45

This diff is too big to show here.

runtime/themes/adwaita-dark.tomlnew file+172

This diff is too big to show here.

runtime/themes/amberwood.tomlnew file+138

This diff is too big to show here.

runtime/themes/autumn.tomlnew file+101

This diff is too big to show here.

runtime/themes/autumn_night.tomlnew file+33

This diff is too big to show here.

runtime/themes/ayu_dark.tomlnew file+89

This diff is too big to show here.

runtime/themes/ayu_evolve.tomlnew file+37

This diff is too big to show here.

runtime/themes/ayu_light.tomlnew file+91

This diff is too big to show here.

runtime/themes/ayu_mirage.tomlnew file+89

This diff is too big to show here.

runtime/themes/base16_default_dark.tomlnew file+77

This diff is too big to show here.

runtime/themes/base16_default_light.tomlnew file+77

This diff is too big to show here.

runtime/themes/base16_terminal.tomlnew file+53

This diff is too big to show here.

runtime/themes/base16_transparent.tomlnew file+79

This diff is too big to show here.

runtime/themes/bogster.tomlnew file+100

This diff is too big to show here.

runtime/themes/bogster_light.tomlnew file+101

This diff is too big to show here.

runtime/themes/boo_berry.tomlnew file+81

This diff is too big to show here.

runtime/themes/catppuccin_frappe.tomlnew file+38

This diff is too big to show here.

runtime/themes/catppuccin_latte.tomlnew file+38

This diff is too big to show here.

runtime/themes/catppuccin_macchiato.tomlnew file+38

This diff is too big to show here.

runtime/themes/catppuccin_mocha.tomlnew file+159

This diff is too big to show here.

runtime/themes/cyan_light.tomlnew file+154

This diff is too big to show here.

runtime/themes/darcula-solid.tomlnew file+16

This diff is too big to show here.

runtime/themes/darcula.tomlnew file+105

This diff is too big to show here.

runtime/themes/dark_high_contrast.tomlnew file+113

This diff is too big to show here.

runtime/themes/dark_plus.tomlnew file+138

This diff is too big to show here.

runtime/themes/doom_acario_dark.tomlnew file+102

This diff is too big to show here.

runtime/themes/dracula.tomlnew file+135

This diff is too big to show here.

runtime/themes/dracula_at_night.tomlnew file+72

This diff is too big to show here.

runtime/themes/emacs.tomlnew file+639

This diff is too big to show here.

runtime/themes/everblush.tomlnew file+115

This diff is too big to show here.

runtime/themes/everforest_dark.tomlnew file+144

This diff is too big to show here.

runtime/themes/everforest_light.tomlnew file+143

This diff is too big to show here.

runtime/themes/ferra.tomlnew file+83

This diff is too big to show here.

runtime/themes/flatwhite.tomlnew file+122

This diff is too big to show here.

runtime/themes/fleet_dark.tomlnew file+166

This diff is too big to show here.

runtime/themes/github_dark.tomlnew file+229

This diff is too big to show here.

runtime/themes/github_dark_colorblind.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_dark_dimmed.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_dark_high_contrast.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_dark_tritanopia.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_light.tomlnew file+229

This diff is too big to show here.

runtime/themes/github_light_colorblind.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_light_high_contrast.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_light_tritanopia.tomlnew file+151

This diff is too big to show here.

runtime/themes/gruber-darker.tomlnew file+114

This diff is too big to show here.

runtime/themes/gruvbox.tomlnew file+108

This diff is too big to show here.

runtime/themes/gruvbox_dark_hard.tomlnew file+13

This diff is too big to show here.

runtime/themes/gruvbox_dark_soft.tomlnew file+7

This diff is too big to show here.

runtime/themes/gruvbox_light.tomlnew file+31

This diff is too big to show here.

runtime/themes/heisenberg.tomlnew file+86

This diff is too big to show here.

runtime/themes/hex_lavender.tomlnew file+31

This diff is too big to show here.

runtime/themes/hex_steel.tomlnew file+115

This diff is too big to show here.

runtime/themes/hex_toxic.tomlnew file+30

This diff is too big to show here.

runtime/themes/horizon-dark.tomlnew file+80

This diff is too big to show here.

runtime/themes/ingrid.tomlnew file+75

This diff is too big to show here.

runtime/themes/jellybeans.tomlnew file+138

This diff is too big to show here.

runtime/themes/kanagawa.tomlnew file+152

This diff is too big to show here.

runtime/themes/kaolin-dark.tomlnew file+108

This diff is too big to show here.

runtime/themes/kaolin-light.tomlnew file+60

This diff is too big to show here.

runtime/themes/kaolin-valley-dark.tomlnew file+73

This diff is too big to show here.

runtime/themes/licenses/everforest.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_deuteranopia.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tinted.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tritanopia.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/poimandres.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/serika-syntax.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/sonokai.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/starlight.LICENSEnew file+24

This diff is too big to show here.

runtime/themes/material_darker.tomlnew file+20

This diff is too big to show here.

runtime/themes/material_deep_ocean.tomlnew file+123

This diff is too big to show here.

runtime/themes/material_oceanic.tomlnew file+21

This diff is too big to show here.

runtime/themes/material_palenight.tomlnew file+19

This diff is too big to show here.

runtime/themes/meliora.tomlnew file+101

This diff is too big to show here.

runtime/themes/mellow.tomlnew file+143

This diff is too big to show here.

runtime/themes/merionette.tomlnew file+98

This diff is too big to show here.

runtime/themes/modus_operandi.tomlnew file+194

This diff is too big to show here.

runtime/themes/modus_operandi_deuteranopia.tomlnew file+46

This diff is too big to show here.

runtime/themes/modus_operandi_tinted.tomlnew file+45

This diff is too big to show here.

runtime/themes/modus_operandi_tritanopia.tomlnew file+73

This diff is too big to show here.

runtime/themes/modus_vivendi.tomlnew file+242

This diff is too big to show here.

runtime/themes/modus_vivendi_deuteranopia.tomlnew file+158

This diff is too big to show here.

runtime/themes/modus_vivendi_tinted.tomlnew file+144

This diff is too big to show here.

runtime/themes/modus_vivendi_tritanopia.tomlnew file+186

This diff is too big to show here.

runtime/themes/molokai.tomlnew file+48

This diff is too big to show here.

runtime/themes/monokai.tomlnew file+113

This diff is too big to show here.

runtime/themes/monokai_aqua.tomlnew file+17

This diff is too big to show here.

runtime/themes/monokai_pro.tomlnew file+126

This diff is too big to show here.

runtime/themes/monokai_pro_machine.tomlnew file+123

This diff is too big to show here.

runtime/themes/monokai_pro_octagon.tomlnew file+126

This diff is too big to show here.

runtime/themes/monokai_pro_ristretto.tomlnew file+123

This diff is too big to show here.

runtime/themes/monokai_pro_spectrum.tomlnew file+123

This diff is too big to show here.

runtime/themes/naysayer.tomlnew file+91

This diff is too big to show here.

runtime/themes/new_moon.tomlnew file+124

This diff is too big to show here.

runtime/themes/night_owl.tomlnew file+114

This diff is too big to show here.

runtime/themes/nightfox.tomlnew file+188

This diff is too big to show here.

runtime/themes/noctis.tomlnew file+207

This diff is too big to show here.

runtime/themes/noctis_bordo.tomlnew file+87

This diff is too big to show here.

runtime/themes/nord-night.tomlnew file+23

This diff is too big to show here.

runtime/themes/nord.tomlnew file+190

This diff is too big to show here.

runtime/themes/nord_light.tomlnew file+83

This diff is too big to show here.

runtime/themes/onedark.tomlnew file+107

This diff is too big to show here.

runtime/themes/onedarker.tomlnew file+105

This diff is too big to show here.

runtime/themes/onelight.tomlnew file+190

This diff is too big to show here.

runtime/themes/papercolor-dark.tomlnew file+75

This diff is too big to show here.

runtime/themes/papercolor-light.tomlnew file+214

This diff is too big to show here.

runtime/themes/penumbra+.tomlnew file+126

This diff is too big to show here.

runtime/themes/poimandres.tomlnew file+113

This diff is too big to show here.

runtime/themes/poimandres_storm.tomlnew file+19

This diff is too big to show here.

runtime/themes/pop-dark.tomlnew file+162

This diff is too big to show here.

runtime/themes/rasmus.tomlnew file+140

This diff is too big to show here.

runtime/themes/rose_pine.tomlnew file+192

This diff is too big to show here.

runtime/themes/rose_pine_dawn.tomlnew file+30

This diff is too big to show here.

runtime/themes/rose_pine_moon.tomlnew file+30

This diff is too big to show here.

runtime/themes/serika-dark.tomlnew file+108

This diff is too big to show here.

runtime/themes/serika-light.tomlnew file+109

This diff is too big to show here.

runtime/themes/snazzy.tomlnew file+116

This diff is too big to show here.

runtime/themes/solarized_dark.tomlnew file+134

This diff is too big to show here.

runtime/themes/solarized_light.tomlnew file+155

This diff is too big to show here.

runtime/themes/sonokai.tomlnew file+113

This diff is too big to show here.

runtime/themes/spacebones_light.tomlnew file+106

This diff is too big to show here.

runtime/themes/starlight.tomlnew file+113

This diff is too big to show here.

runtime/themes/tokyonight.tomlnew file+96

This diff is too big to show here.

runtime/themes/tokyonight_storm.tomlnew file+8

This diff is too big to show here.

runtime/themes/varua.tomlnew file+104

This diff is too big to show here.

runtime/themes/vim_dark_high_contrast.tomlnew file+72

This diff is too big to show here.

runtime/themes/yellowed.tomlnew file+135

This diff is too big to show here.

runtime/themes/zed_onedark.tomlnew file+110

This diff is too big to show here.

runtime/themes/zed_onelight.tomlnew file+55

This diff is too big to show here.

runtime/themes/zenburn.tomlnew file+63

This diff is too big to show here.

runtime/tutornew file+1345

This diff is too big to show here.

rust-toolchain.tomlnew file+3

This diff is too big to show here.

rustfmt.tomlnew file

No changes to the content.

screenshot.pngnew file

Binary file not shown.

shell.nixnew file+8

This diff is too big to show here.

theme.tomlnew file+107

This diff is too big to show here.

xtask/Cargo.tomlnew file+19

This diff is too big to show here.

xtask/src/docgen.rsnew file+118

This diff is too big to show here.

xtask/src/helpers.rsnew file+44

This diff is too big to show here.

xtask/src/main.rsnew file+61

This diff is too big to show here.

xtask/src/path.rsnew file+24

This diff is too big to show here.

xtask/src/querycheck.rsnew file+39

This diff is too big to show here.

xtask/src/themelint.rsnew file+200

This diff is too big to show here.