mirror of
https://github.com/helix-editor/helix.git
synced 2025-10-06 08:23:27 +02:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7a51085e8a |
2
.cargo/config
Normal file
2
.cargo/config
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
@@ -1,17 +0,0 @@
|
||||
# we use tokio_unstable to enable runtime::Handle::id so we can separate
|
||||
# globals from multiple parallel tests. If that function ever does get removed
|
||||
# its possible to replace (with some additional overhead and effort)
|
||||
# Annoyingly build.rustflags doesn't work here because it gets overwritten
|
||||
# if people have their own global target.<..> config (for example to enable mold)
|
||||
# specifying flags this way is more robust as they get merged
|
||||
# This still gets overwritten by RUST_FLAGS though, luckily it shouldn't be necessary
|
||||
# to set those most of the time. If downstream does overwrite this its not a huge
|
||||
# deal since it will only break tests anyway
|
||||
[target."cfg(all())"]
|
||||
rustflags = ["--cfg", "tokio_unstable", "-C", "target-feature=-crt-static"]
|
||||
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
integration-test = "test --features integration --profile integration --workspace --test integration"
|
||||
|
2
.envrc
2
.envrc
@@ -1,7 +1,5 @@
|
||||
watch_file shell.nix
|
||||
watch_file default.nix
|
||||
watch_file flake.lock
|
||||
watch_file rust-toolchain.toml
|
||||
|
||||
# try to use flakes, if it fails use normal nix (ie. shell.nix)
|
||||
use flake || use nix
|
||||
|
12
.gitattributes
vendored
12
.gitattributes
vendored
@@ -1,12 +0,0 @@
|
||||
# Auto detect text files and perform normalization
|
||||
* text=auto
|
||||
|
||||
*.rs text diff=rust
|
||||
*.toml text diff=toml
|
||||
|
||||
*.scm text diff=scheme
|
||||
*.md text diff=markdown
|
||||
|
||||
book/theme/highlight.js linguist-vendored
|
||||
runtime/queries/**/*.scm linguist-language=Tree-sitter-Query
|
||||
Cargo.lock text
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
open_collective: helix-editor
|
4
.github/ISSUE_TEMPLATE/blank_issue.md
vendored
Normal file
4
.github/ISSUE_TEMPLATE/blank_issue.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
name: Blank Issue
|
||||
about: Create a blank issue.
|
||||
---
|
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: C-bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Your issue may already be reported!
|
||||
Please search on the issue tracker before creating one. -->
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
<!-- Ideally provide a key sequence and/or asciinema.org recording. -->
|
||||
|
||||
### Environment
|
||||
|
||||
- Platform: <!-- macOS / Windows / Linux -->
|
||||
- Terminal emulator:
|
||||
- Helix version: <!-- 'hx -V' if using a release, 'git describe' if building from master -->
|
||||
|
||||
<details><summary>~/.cache/helix/helix.log</summary>
|
||||
|
||||
```
|
||||
please provide a copy of `~/.cache/helix/helix.log` here if possible, you may need to redact some of the lines
|
||||
```
|
||||
|
||||
</details>
|
78
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
78
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -1,78 +0,0 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
labels: C-bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thank you for filing a bug report! 🐛
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Summary
|
||||
description: >
|
||||
Please provide a short summary of the bug, along with any information
|
||||
you feel relevant to replicate the bug.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction-steps
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
value: |
|
||||
<!-- Ideally provide a key sequence and/or asciinema.org recording. -->
|
||||
|
||||
I tried this:
|
||||
|
||||
1. `hx`
|
||||
|
||||
I expected this to happen:
|
||||
|
||||
Instead, this happened:
|
||||
- type: textarea
|
||||
id: helix-log
|
||||
attributes:
|
||||
label: Helix log
|
||||
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.
|
||||
value: |
|
||||
<details><summary>~/.cache/helix/helix.log</summary>
|
||||
|
||||
```
|
||||
please provide a copy of `~/.cache/helix/helix.log` here if possible, you may need to redact some of the lines
|
||||
```
|
||||
|
||||
</details>
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
placeholder: Linux / macOS / Windows
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: terminal-emulator
|
||||
attributes:
|
||||
label: Terminal Emulator
|
||||
placeholder: wezterm 20220101-133340-7edc5b5a
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: installation-method
|
||||
attributes:
|
||||
label: Installation Method
|
||||
description: >
|
||||
How you installed Helix - from a package manager like Homebrew or the
|
||||
AUR, built from source, downloaded a binary from the releases page, etc.
|
||||
placeholder: "source / brew / nixpkgs / flake / releases page"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: helix-version
|
||||
attributes:
|
||||
label: Helix Version
|
||||
description: >
|
||||
Helix version (`hx -V` if using a release, `git describe` if building
|
||||
from master).
|
||||
**Make sure that you are using the [latest helix release](https://github.com/helix-editor/helix/releases) or a newer master build**
|
||||
placeholder: "helix 22.12 (5eaa6d97)"
|
||||
validations:
|
||||
required: true
|
13
.github/ISSUE_TEMPLATE/enhancement.md
vendored
13
.github/ISSUE_TEMPLATE/enhancement.md
vendored
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: Enhancement
|
||||
about: Suggest an improvement
|
||||
title: ''
|
||||
labels: C-enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
Your enhancement may already be reported!
|
||||
Please search on the issue tracker before creating a new issue.
|
||||
If this is an idea for a feature, please open an "Idea" Discussion instead.
|
||||
-->
|
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a new feature or improvement
|
||||
title: ''
|
||||
labels: C-enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!-- Your feature may already be reported!
|
||||
Please search on the issue tracker before creating one. -->
|
||||
|
||||
#### Describe your feature request
|
||||
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -7,11 +7,6 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
rust-dependencies:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
|
202
.github/workflows/build.yml
vendored
202
.github/workflows/build.yml
vendored
@@ -4,149 +4,194 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
merge_group:
|
||||
schedule:
|
||||
- cron: "00 01 * * *"
|
||||
|
||||
env:
|
||||
MSRV: "1.82"
|
||||
# This key can be changed to bust the cache of tree-sitter grammars.
|
||||
GRAMMAR_CACHE_VERSION: ""
|
||||
- cron: '00 01 * * *'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check (msrv)
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.MSRV }}
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
shared-key: "build"
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-registry-
|
||||
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-index-
|
||||
|
||||
- name: Cache cargo target dir
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-build-target-
|
||||
|
||||
- name: Run cargo check
|
||||
run: cargo check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
HELIX_LOG_LEVEL: info
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.MSRV }}
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
shared-key: "build"
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-registry-
|
||||
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-index-
|
||||
|
||||
- name: Cache cargo target dir
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-build-target-
|
||||
|
||||
- name: Copy minimal languages config
|
||||
run: cp .github/workflows/languages.toml ./languages.toml
|
||||
|
||||
- name: Cache test tree-sitter grammar
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
key: ${{ runner.os }}-v2-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-v2-tree-sitter-grammars-
|
||||
|
||||
- name: Run cargo test
|
||||
run: cargo test --workspace
|
||||
|
||||
- name: Run cargo integration-test
|
||||
run: cargo integration-test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest, ubuntu-24.04-arm]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
rust: [stable]
|
||||
|
||||
lints:
|
||||
name: Lints
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.MSRV }}
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
shared-key: "build"
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-registry-
|
||||
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-index-
|
||||
|
||||
- name: Cache cargo target dir
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-build-target-
|
||||
|
||||
- name: Run cargo fmt
|
||||
run: cargo fmt --all --check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Run cargo clippy
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: Run cargo doc
|
||||
run: cargo doc --no-deps --workspace --document-private-items
|
||||
env:
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets -- -D warnings
|
||||
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.MSRV }}
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
shared-key: "build"
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-registry-
|
||||
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-index-
|
||||
|
||||
- name: Validate queries
|
||||
run: cargo xtask query-check
|
||||
|
||||
- name: Validate themes
|
||||
run: cargo xtask theme-check
|
||||
- name: Cache cargo target dir
|
||||
uses: actions/cache@v2.1.6
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-v2-cargo-build-target-
|
||||
|
||||
- name: Generate docs
|
||||
run: cargo xtask docgen
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: xtask
|
||||
args: docgen
|
||||
|
||||
- name: Check uncommitted documentation changes
|
||||
run: |
|
||||
@@ -154,3 +199,4 @@ jobs:
|
||||
git diff-files --quiet \
|
||||
|| (echo "Run 'cargo xtask docgen', commit the changes and push again" \
|
||||
&& exit 1)
|
||||
|
||||
|
8
.github/workflows/cachix.yml
vendored
8
.github/workflows/cachix.yml
vendored
@@ -11,16 +11,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
uses: cachix/install-nix-action@v16
|
||||
|
||||
- name: Authenticate with Cachix
|
||||
uses: cachix/cachix-action@v16
|
||||
uses: cachix/cachix-action@v10
|
||||
with:
|
||||
name: helix
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
- name: Build nix flake
|
||||
run: nix build -L
|
||||
run: nix build
|
||||
|
26
.github/workflows/gh-pages.yml
vendored
26
.github/workflows/gh-pages.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: GitHub Pages
|
||||
name: Github Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,13 +11,13 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v2
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
with:
|
||||
# mdbook-version: 'latest'
|
||||
mdbook-version: '0.4.43'
|
||||
mdbook-version: 'latest'
|
||||
# mdbook-version: '0.4.8'
|
||||
|
||||
- run: mdbook build book
|
||||
|
||||
@@ -26,16 +26,16 @@ jobs:
|
||||
OUTDIR=$(basename ${{ github.ref }})
|
||||
echo "OUTDIR=$OUTDIR" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy stable
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
if: startswith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./book/book
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./book/book
|
||||
destination_dir: ./${{ env.OUTDIR }}
|
||||
|
||||
- name: Deploy stable
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: startswith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./book/book
|
||||
|
2
.github/workflows/languages.toml
vendored
2
.github/workflows/languages.toml
vendored
@@ -11,7 +11,7 @@ indent = { tab-width = 4, unit = " " }
|
||||
|
||||
[[grammar]]
|
||||
name = "rust"
|
||||
source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
|
||||
source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "a360da0a29a19c281d08295a35ecd0544d2da211" }
|
||||
|
||||
[[language]]
|
||||
name = "nix"
|
||||
|
270
.github/workflows/release.yml
vendored
270
.github/workflows/release.yml
vendored
@@ -1,98 +1,46 @@
|
||||
name: Release
|
||||
on:
|
||||
# schedule:
|
||||
# - cron: '0 0 * * *' # midnight UTC
|
||||
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
branches:
|
||||
- 'patch/ci-release-*'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/release.yml'
|
||||
|
||||
env:
|
||||
# Preview mode: Publishes the build output as a CI artifact instead of creating
|
||||
# a release, allowing for manual inspection of the output. This mode is
|
||||
# activated if the CI run was triggered by events other than pushed tags, or
|
||||
# if the repository is a fork.
|
||||
preview: ${{ !startsWith(github.ref, 'refs/tags/') || github.repository != 'helix-editor/helix' }}
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
## - release
|
||||
|
||||
jobs:
|
||||
fetch-grammars:
|
||||
name: Fetch Grammars
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Fetch tree-sitter grammars
|
||||
run: cargo run --package=helix-loader --bin=hx-loader
|
||||
|
||||
- name: Bundle grammars
|
||||
run: tar cJf grammars.tar.xz -C runtime/grammars/sources .
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: grammars
|
||||
path: grammars.tar.xz
|
||||
|
||||
dist:
|
||||
name: Dist
|
||||
needs: [fetch-grammars]
|
||||
env:
|
||||
# For some builds, we use cross to test on 32-bit and big-endian
|
||||
# systems.
|
||||
CARGO: cargo
|
||||
# When CARGO is set to CROSS, this is set to `--target matrix.target`.
|
||||
TARGET_FLAGS:
|
||||
# When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
|
||||
TARGET_DIR: ./target
|
||||
# Emit backtraces on panics.
|
||||
RUST_BACKTRACE: 1
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false # don't fail other jobs if one fails
|
||||
matrix:
|
||||
build: [x86_64-linux, aarch64-linux, x86_64-macos, x86_64-windows, aarch64-macos] #, x86_64-win-gnu, win32-msvc
|
||||
build: [x86_64-linux, aarch64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
|
||||
include:
|
||||
- build: x86_64-linux
|
||||
# WARN: When changing this to a newer version, make sure that the GLIBC isnt too new, as this can cause issues
|
||||
# with portablity on older systems that dont follow ubuntus more rapid release cadence.
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-20.04
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
cross: false
|
||||
- build: aarch64-linux
|
||||
# Version should be kept in lockstep with the x86_64 version
|
||||
os: ubuntu-22.04-arm
|
||||
os: ubuntu-20.04
|
||||
rust: stable
|
||||
target: aarch64-unknown-linux-gnu
|
||||
cross: false
|
||||
cross: true
|
||||
- build: x86_64-macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
target: x86_64-apple-darwin
|
||||
cross: false
|
||||
- build: x86_64-windows
|
||||
os: windows-latest
|
||||
os: windows-2019
|
||||
rust: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
cross: false
|
||||
- build: aarch64-macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
target: aarch64-apple-darwin
|
||||
cross: false
|
||||
# - build: riscv64-linux
|
||||
# os: ubuntu-22.04
|
||||
# - build: aarch64-macos
|
||||
# os: macos-latest
|
||||
# rust: stable
|
||||
# target: riscv64gc-unknown-linux-gnu
|
||||
# cross: true
|
||||
# target: aarch64-apple-darwin
|
||||
# - build: x86_64-win-gnu
|
||||
# os: windows-2019
|
||||
# rust: stable-x86_64-gnu
|
||||
@@ -104,127 +52,55 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download grammars
|
||||
uses: actions/download-artifact@v5
|
||||
|
||||
- name: Move grammars under runtime
|
||||
if: "!startsWith(matrix.os, 'windows')"
|
||||
run: |
|
||||
mkdir -p runtime/grammars/sources
|
||||
tar xJf grammars.tar.xz -C runtime/grammars/sources
|
||||
|
||||
# The rust-toolchain action ignores rust-toolchain.toml files.
|
||||
# Removing this before building with cargo ensures that the rust-toolchain
|
||||
# is considered the same between installation and usage.
|
||||
- name: Remove the rust-toolchain.toml file
|
||||
run: rm rust-toolchain.toml
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install ${{ matrix.rust }} toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
# Install a pre-release version of Cross
|
||||
# TODO: We need to pre-install Cross because we need cross-rs/cross#591 to
|
||||
# get a newer C++ compiler toolchain. Remove this step when Cross
|
||||
# 0.3.0, which includes cross-rs/cross#591, is released.
|
||||
- name: Install Cross
|
||||
if: "matrix.cross"
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross.git --rev 47df5c76e7cba682823a0b6aa6d95c17b31ba63a
|
||||
echo "CARGO=cross" >> $GITHUB_ENV
|
||||
# echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
|
||||
# echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Show command used for Cargo
|
||||
run: |
|
||||
echo "cargo command is: ${{ env.CARGO }}"
|
||||
echo "target flag is: ${{ env.TARGET_FLAGS }}"
|
||||
override: true
|
||||
|
||||
- name: Run cargo test
|
||||
if: "!matrix.skip_tests"
|
||||
run: ${{ env.CARGO }} test --release --locked --target ${{ matrix.target }} --workspace
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.cross }}
|
||||
command: test
|
||||
args: --release --locked --target ${{ matrix.target }} --workspace
|
||||
|
||||
- name: Build release binary
|
||||
run: ${{ env.CARGO }} build --profile opt --locked --target ${{ matrix.target }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.cross }}
|
||||
command: build
|
||||
args: --release --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Build AppImage
|
||||
shell: bash
|
||||
if: matrix.build == 'x86_64-linux'
|
||||
- name: Strip release binary (linux and macos)
|
||||
if: matrix.build == 'x86_64-linux' || matrix.build == 'x86_64-macos'
|
||||
run: strip "target/${{ matrix.target }}/release/hx"
|
||||
|
||||
- name: Strip release binary (arm)
|
||||
if: matrix.build == 'aarch64-linux'
|
||||
run: |
|
||||
# Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install libfuse2
|
||||
|
||||
mkdir dist
|
||||
|
||||
name=dev
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
name=${GITHUB_REF:10}
|
||||
fi
|
||||
|
||||
build="${{ matrix.build }}"
|
||||
|
||||
export VERSION="$name"
|
||||
export ARCH=${build%-linux}
|
||||
export APP=helix
|
||||
export OUTPUT="helix-$VERSION-$ARCH.AppImage"
|
||||
export UPDATE_INFORMATION="gh-releases-zsync|$GITHUB_REPOSITORY_OWNER|helix|latest|$APP-*-$ARCH.AppImage.zsync"
|
||||
|
||||
mkdir -p "$APP.AppDir"/usr/{bin,lib/helix}
|
||||
|
||||
cp "target/${{ matrix.target }}/opt/hx" "$APP.AppDir/usr/bin/hx"
|
||||
rm -rf runtime/grammars/sources
|
||||
cp -r runtime "$APP.AppDir/usr/lib/helix/runtime"
|
||||
|
||||
cat << 'EOF' > "$APP.AppDir/AppRun"
|
||||
#!/bin/sh
|
||||
|
||||
APPDIR="$(dirname "$(readlink -f "${0}")")"
|
||||
HELIX_RUNTIME="$APPDIR/usr/lib/helix/runtime" exec "$APPDIR/usr/bin/hx" "$@"
|
||||
EOF
|
||||
chmod 755 "$APP.AppDir/AppRun"
|
||||
|
||||
curl -Lo linuxdeploy-x86_64.AppImage \
|
||||
https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
chmod +x linuxdeploy-x86_64.AppImage
|
||||
|
||||
./linuxdeploy-x86_64.AppImage \
|
||||
--appdir "$APP.AppDir" -d contrib/Helix.desktop \
|
||||
-i contrib/helix.png --output appimage
|
||||
|
||||
mv "$APP-$VERSION-$ARCH.AppImage" \
|
||||
"$APP-$VERSION-$ARCH.AppImage.zsync" dist
|
||||
|
||||
- name: Build Deb
|
||||
shell: bash
|
||||
if: matrix.build == 'x86_64-linux'
|
||||
run: |
|
||||
cargo install cargo-deb
|
||||
mkdir -p target/release
|
||||
cp target/${{ matrix.target }}/opt/hx target/release/
|
||||
cargo deb --no-build
|
||||
mkdir -p dist
|
||||
mv target/debian/*.deb dist/
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
rustembedded/cross:${{ matrix.target }} \
|
||||
aarch64-linux-gnu-strip \
|
||||
/target/${{ matrix.target }}/release/hx
|
||||
|
||||
- name: Build archive
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p dist
|
||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||
cp "target/${{ matrix.target }}/opt/hx.exe" "dist/"
|
||||
mkdir dist
|
||||
if [ "${{ matrix.os }}" = "windows-2019" ]; then
|
||||
cp "target/${{ matrix.target }}/release/hx.exe" "dist/"
|
||||
else
|
||||
cp "target/${{ matrix.target }}/opt/hx" "dist/"
|
||||
fi
|
||||
if [ -d runtime/grammars/sources ]; then
|
||||
rm -rf runtime/grammars/sources
|
||||
cp "target/${{ matrix.target }}/release/hx" "dist/"
|
||||
fi
|
||||
cp -r runtime dist
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bins-${{ matrix.build }}
|
||||
path: dist
|
||||
@@ -235,23 +111,27 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v2
|
||||
|
||||
- name: Calculate tag name
|
||||
run: |
|
||||
name=dev
|
||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
||||
name=${GITHUB_REF:10}
|
||||
fi
|
||||
echo ::set-output name=val::$name
|
||||
echo TAG=$name >> $GITHUB_ENV
|
||||
id: tagname
|
||||
|
||||
- name: Build archive
|
||||
shell: bash
|
||||
run: |
|
||||
set -ex
|
||||
|
||||
source="$(pwd)"
|
||||
tag=${GITHUB_REF_NAME//\//}
|
||||
mkdir -p runtime/grammars/sources
|
||||
tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
|
||||
rm -rf grammars
|
||||
|
||||
cd "$(mktemp -d)"
|
||||
mv $source/bins-* .
|
||||
rm -rf tmp
|
||||
mkdir tmp
|
||||
mkdir dist
|
||||
|
||||
for dir in bins-* ; do
|
||||
@@ -259,43 +139,25 @@ jobs:
|
||||
if [[ $platform =~ "windows" ]]; then
|
||||
exe=".exe"
|
||||
fi
|
||||
pkgname=helix-$tag-$platform
|
||||
mkdir -p $pkgname
|
||||
cp $source/LICENSE $source/README.md $pkgname
|
||||
mkdir $pkgname/contrib
|
||||
cp -r $source/contrib/completion $pkgname/contrib
|
||||
mv bins-$platform/runtime $pkgname/
|
||||
mv bins-$platform/hx$exe $pkgname
|
||||
chmod +x $pkgname/hx$exe
|
||||
|
||||
if [[ "$platform" = "x86_64-linux" ]]; then
|
||||
mv bins-$platform/helix-*.AppImage* dist/
|
||||
mv bins-$platform/*.deb dist/
|
||||
fi
|
||||
pkgname=helix-$TAG-$platform
|
||||
mkdir tmp/$pkgname
|
||||
cp LICENSE README.md tmp/$pkgname
|
||||
mv bins-$platform/runtime tmp/$pkgname/
|
||||
mv bins-$platform/hx$exe tmp/$pkgname
|
||||
chmod +x tmp/$pkgname/hx$exe
|
||||
|
||||
if [ "$exe" = "" ]; then
|
||||
tar cJf dist/$pkgname.tar.xz $pkgname
|
||||
tar cJf dist/$pkgname.tar.xz -C tmp $pkgname
|
||||
else
|
||||
7z a -r dist/$pkgname.zip $pkgname
|
||||
(cd tmp && 7z a -r ../dist/$pkgname.zip $pkgname)
|
||||
fi
|
||||
done
|
||||
|
||||
tar cJf dist/helix-$tag-source.tar.xz -C $source .
|
||||
mv dist $source/
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: env.preview == 'false'
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: dist/*
|
||||
file_glob: true
|
||||
tag: ${{ github.ref_name }}
|
||||
tag: ${{ steps.tagname.outputs.val }}
|
||||
overwrite: true
|
||||
|
||||
- name: Upload binaries as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: env.preview == 'true'
|
||||
with:
|
||||
name: release
|
||||
path: dist/*
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
target
|
||||
.direnv
|
||||
helix-term/rustfmt.toml
|
||||
helix-syntax/languages/
|
||||
result
|
||||
runtime/grammars
|
||||
.DS_Store
|
||||
|
2698
CHANGELOG.md
2698
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
3065
Cargo.lock
generated
3065
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
45
Cargo.toml
45
Cargo.toml
@@ -1,18 +1,12 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"helix-core",
|
||||
"helix-view",
|
||||
"helix-term",
|
||||
"helix-tui",
|
||||
"helix-lsp-types",
|
||||
"helix-lsp",
|
||||
"helix-event",
|
||||
"helix-dap",
|
||||
"helix-loader",
|
||||
"helix-vcs",
|
||||
"helix-parsec",
|
||||
"helix-stdx",
|
||||
"xtask",
|
||||
]
|
||||
|
||||
@@ -20,45 +14,16 @@ default-members = [
|
||||
"helix-term"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
# debug = true
|
||||
|
||||
[profile.opt]
|
||||
inherits = "release"
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
# strip = "debuginfo" # TODO: or strip = true
|
||||
opt-level = 3
|
||||
|
||||
[profile.integration]
|
||||
inherits = "test"
|
||||
package.helix-core.opt-level = 2
|
||||
package.helix-tui.opt-level = 2
|
||||
package.helix-term.opt-level = 2
|
||||
|
||||
[workspace.dependencies]
|
||||
tree-house = { version = "0.3.0", default-features = false }
|
||||
nucleo = "0.5.0"
|
||||
slotmap = "1.0.7"
|
||||
thiserror = "2.0"
|
||||
tempfile = "3.23.0"
|
||||
bitflags = "2.9"
|
||||
unicode-segmentation = "1.2"
|
||||
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
|
||||
foldhash = "0.2"
|
||||
parking_lot = "0.12"
|
||||
futures-executor = "0.3"
|
||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||
tokio-stream = "0.1.17"
|
||||
toml = "0.9"
|
||||
termina = "0.1"
|
||||
|
||||
[workspace.package]
|
||||
version = "25.7.1"
|
||||
edition = "2021"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
categories = ["editor"]
|
||||
repository = "https://github.com/helix-editor/helix"
|
||||
homepage = "https://helix-editor.com"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.82"
|
||||
|
65
README.md
65
README.md
@@ -1,27 +1,14 @@
|
||||
<div align="center">
|
||||
# Helix
|
||||
|
||||
<h1>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="logo_dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="logo_light.svg">
|
||||
<img alt="Helix" height="128" src="logo_light.svg">
|
||||
</picture>
|
||||
</h1>
|
||||
|
||||
[](https://github.com/helix-editor/helix/actions)
|
||||
[](https://github.com/helix-editor/helix/releases/latest)
|
||||
[](https://docs.helix-editor.com/)
|
||||
[](https://github.com/helix-editor/helix/graphs/contributors)
|
||||
[](https://matrix.to/#/#helix-community:matrix.org)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
A [Kakoune](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/neovim) inspired editor, written in Rust.
|
||||
A kakoune / neovim inspired editor, written in Rust.
|
||||
|
||||
The editing model is very heavily based on Kakoune; during development I found
|
||||
myself agreeing with most of Kakoune's design decisions.
|
||||
The editing model is very heavily based on kakoune; during development I found
|
||||
myself agreeing with most of kakoune's design decisions.
|
||||
|
||||
For more information, see the [website](https://helix-editor.com) or
|
||||
[documentation](https://docs.helix-editor.com/).
|
||||
@@ -37,17 +24,47 @@ All shortcuts/keymaps can be found [in the documentation on the website](https:/
|
||||
- Built-in language server support
|
||||
- Smart, incremental syntax highlighting and code editing via tree-sitter
|
||||
|
||||
Although it's primarily a terminal-based editor, I am interested in exploring
|
||||
a custom renderer (similar to Emacs) using wgpu or skulpin.
|
||||
It's a terminal-based editor first, but I'd like to explore a custom renderer
|
||||
(similar to emacs) in wgpu or skulpin.
|
||||
|
||||
Note: Only certain languages have indentation definitions at the moment. Check
|
||||
`runtime/queries/<lang>/` for `indents.scm`.
|
||||
`runtime/queries/<lang>/` for `indents.toml`.
|
||||
|
||||
# Installation
|
||||
|
||||
[Installation documentation](https://docs.helix-editor.com/install.html).
|
||||
We provide packaging for various distributions, but here's a quick method to
|
||||
build from source.
|
||||
|
||||
[](https://repology.org/project/helix-editor/versions)
|
||||
```
|
||||
git clone https://github.com/helix-editor/helix
|
||||
cd helix
|
||||
cargo install --path helix-term
|
||||
hx --grammar fetch
|
||||
hx --grammar build
|
||||
```
|
||||
|
||||
This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars.
|
||||
|
||||
Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
|
||||
config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows).
|
||||
This location can be overridden via the `HELIX_RUNTIME` environment variable.
|
||||
|
||||
Packages already solve this for you by wrapping the `hx` binary with a wrapper
|
||||
that sets the variable to the install dir.
|
||||
|
||||
> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
|
||||
> detect the `runtime` directory in the project root.
|
||||
|
||||
[](https://repology.org/project/helix/versions)
|
||||
|
||||
## MacOS
|
||||
|
||||
Helix can be installed on MacOS through homebrew via:
|
||||
|
||||
```
|
||||
brew tap helix-editor/helix
|
||||
brew install helix
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
@@ -58,7 +75,3 @@ Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
|
||||
Your question might already be answered on the [FAQ](https://github.com/helix-editor/helix/wiki/FAQ).
|
||||
|
||||
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).
|
||||
|
||||
# Credits
|
||||
|
||||
Thanks to [@jakenvac](https://github.com/jakenvac) for designing the logo!
|
||||
|
@@ -1,16 +1,16 @@
|
||||
# Author: NNB <nnbnh@protonmail.com>
|
||||
|
||||
"ui.menu" = { fg = "black", bg = "white" }
|
||||
"ui.menu" = "black"
|
||||
"ui.menu.selected" = { modifiers = ["reversed"] }
|
||||
"ui.linenr" = { fg = "gray", bg = "black" }
|
||||
"ui.popup" = { modifiers = ["reversed"] }
|
||||
"ui.linenr.selected" = { fg = "white", bg = "black", modifiers = ["bold"] }
|
||||
"ui.selection" = { fg = "black", bg = "blue" }
|
||||
"ui.selection.primary" = { fg = "white", bg = "blue" }
|
||||
"ui.text.inactive" = { fg = "gray" }
|
||||
"comment" = { fg = "gray" }
|
||||
"ui.statusline" = { fg = "black", bg = "white" }
|
||||
"ui.statusline.inactive" = { fg = "gray", bg = "white" }
|
||||
"ui.help" = { modifiers = ["reversed"] }
|
||||
"ui.cursor" = { fg = "white", modifiers = ["reversed"] }
|
||||
"variable" = "red"
|
||||
"constant.numeric" = "yellow"
|
||||
@@ -28,11 +28,6 @@
|
||||
"label" = "magenta"
|
||||
"namespace" = "magenta"
|
||||
"ui.help" = { fg = "white", bg = "black" }
|
||||
"ui.statusline.insert" = { fg = "black", bg = "green" }
|
||||
"ui.statusline.select" = { fg = "black", bg = "blue" }
|
||||
"ui.virtual" = { fg = "gray", modifiers = ["italic"] }
|
||||
"ui.virtual.jump-label" = { fg = "blue", modifiers = ["bold", "underlined"] }
|
||||
"ui.virtual.ruler" = { bg = "black" }
|
||||
|
||||
"markup.heading" = "blue"
|
||||
"markup.list" = "red"
|
||||
|
@@ -1,15 +1,11 @@
|
||||
[book]
|
||||
authors = ["Blaž Hrastnik"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
|
||||
|
||||
[output.html]
|
||||
cname = "docs.helix-editor.com"
|
||||
default-theme = "colibri"
|
||||
preferred-dark-theme = "colibri"
|
||||
git-repository-url = "https://github.com/helix-editor/helix"
|
||||
edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
|
||||
additional-css = ["custom.css"]
|
||||
|
||||
[output.html.search]
|
||||
use-boolean-and = true
|
||||
|
231
book/custom.css
231
book/custom.css
@@ -1,231 +0,0 @@
|
||||
html {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
|
||||
.sidebar .sidebar-scrollbox {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.chapter li.chapter-item {
|
||||
line-height: initial;
|
||||
margin: 0;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.chapter .section li.chapter-item {
|
||||
line-height: inherit;
|
||||
padding: .5rem .5rem 0 .5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
padding: 0 15px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
/* 2 1.75 1.5 1.25 1 .875 */
|
||||
.content h1 { font-size: 2em }
|
||||
.content h2 { font-size: 1.75em }
|
||||
.content h3 { font-size: 1.5em }
|
||||
.content h4 { font-size: 1.25em }
|
||||
.content h5 { font-size: 1em }
|
||||
.content h6 { font-size: .875em }
|
||||
|
||||
.content h1,
|
||||
.content h2,
|
||||
.content h3,
|
||||
.content h4 {
|
||||
font-weight: 500;
|
||||
margin-top: 1.275em;
|
||||
margin-bottom: .875em;
|
||||
}
|
||||
|
||||
.content p,
|
||||
.content ol,
|
||||
.content ul,
|
||||
.content table {
|
||||
margin-top: 0;
|
||||
margin-bottom: .875em;
|
||||
}
|
||||
|
||||
.content ul li {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.content ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
.content ul ul,
|
||||
.content ol ul {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.content li p {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1.5rem 0;
|
||||
padding: 1rem 1.5rem;
|
||||
color: var(--fg);
|
||||
opacity: .9;
|
||||
background-color: var(--quote-bg);
|
||||
border-left: 4px solid var(--quote-border);
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
blockquote *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
padding: .75rem;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: .75rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
table thead tr {
|
||||
border: none;
|
||||
border-bottom: 2px var(--table-border-color) solid;
|
||||
}
|
||||
|
||||
table tbody tr {
|
||||
border-bottom: 1px var(--table-border-line) solid;
|
||||
}
|
||||
|
||||
table tbody tr:nth-child(2n) {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.colibri {
|
||||
--bg: #3b224c;
|
||||
--fg: #bcbdd0;
|
||||
--heading-fg: #fff;
|
||||
|
||||
--sidebar-bg: #281733;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existent: #505274;
|
||||
--sidebar-active: #a4a0e8;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
/* --links: #a4a0e8; */
|
||||
--links: #ECCDBA;
|
||||
|
||||
--inline-code-color: hsl(48.7, 7.8%, 70%);
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
--theme-hover: rgba(0, 0, 0, .2);
|
||||
|
||||
--quote-bg: #281733;
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(226, 23%, 76%);
|
||||
--table-header-bg: hsla(226, 23%, 31%, 0);
|
||||
--table-alternate-bg: hsl(226, 23%, 14%);
|
||||
--table-border-line: hsla(201deg, 20%, 92%, 0.2);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #aeaec6;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #5f5f71;
|
||||
--searchresults-border-color: #5c5c68;
|
||||
--searchresults-li-bg: #242430;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.colibri .content .header {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* highlight.js theme, :where() is used to avoid increasing specificity */
|
||||
|
||||
:where(.colibri) .hljs {
|
||||
background: #2f1e2e;
|
||||
color: #a39e9b;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-comment,
|
||||
:where(.colibri) .hljs-quote {
|
||||
color: #8d8687;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-link,
|
||||
:where(.colibri) .hljs-meta,
|
||||
:where(.colibri) .hljs-name,
|
||||
:where(.colibri) .hljs-regexp,
|
||||
:where(.colibri) .hljs-selector-class,
|
||||
:where(.colibri) .hljs-selector-id,
|
||||
:where(.colibri) .hljs-tag,
|
||||
:where(.colibri) .hljs-template-variable,
|
||||
:where(.colibri) .hljs-variable {
|
||||
color: #ef6155;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-built_in,
|
||||
:where(.colibri) .hljs-deletion,
|
||||
:where(.colibri) .hljs-literal,
|
||||
:where(.colibri) .hljs-number,
|
||||
:where(.colibri) .hljs-params,
|
||||
:where(.colibri) .hljs-type {
|
||||
color: #f99b15;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-attribute,
|
||||
:where(.colibri) .hljs-section,
|
||||
:where(.colibri) .hljs-title {
|
||||
color: #fec418;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-addition,
|
||||
:where(.colibri) .hljs-bullet,
|
||||
:where(.colibri) .hljs-string,
|
||||
:where(.colibri) .hljs-symbol {
|
||||
color: #48b685;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-keyword,
|
||||
:where(.colibri) .hljs-selector-tag {
|
||||
color: #815ba4;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
:where(.colibri) .hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
@@ -3,31 +3,16 @@
|
||||
[Helix](./title-page.md)
|
||||
|
||||
- [Installation](./install.md)
|
||||
- [Package Managers](./package-managers.md)
|
||||
- [Building from source](./building-from-source.md)
|
||||
- [Usage](./usage.md)
|
||||
- [Registers](./registers.md)
|
||||
- [Surround](./surround.md)
|
||||
- [Textobjects](./textobjects.md)
|
||||
- [Syntax aware motions](./syntax-aware-motions.md)
|
||||
- [Pickers](./pickers.md)
|
||||
- [Jumplist](./jumplist.md)
|
||||
- [Keymap](./keymap.md)
|
||||
- [Command line](./command-line.md)
|
||||
- [Commands](./commands.md)
|
||||
- [Language support](./lang-support.md)
|
||||
- [Ecosystem](./ecosystem.md)
|
||||
- [Language Support](./lang-support.md)
|
||||
- [Migrating from Vim](./from-vim.md)
|
||||
- [Helix mode in other software](./other-software.md)
|
||||
- [Configuration](./configuration.md)
|
||||
- [Editor](./editor.md)
|
||||
- [Themes](./themes.md)
|
||||
- [Key remapping](./remapping.md)
|
||||
- [Key Remapping](./remapping.md)
|
||||
- [Hooks](./hooks.md)
|
||||
- [Languages](./languages.md)
|
||||
- [Guides](./guides/README.md)
|
||||
- [Adding languages](./guides/adding_languages.md)
|
||||
- [Adding textobject queries](./guides/textobject.md)
|
||||
- [Adding indent queries](./guides/indent.md)
|
||||
- [Adding injection queries](./guides/injection.md)
|
||||
- [Adding tags queries](./guides/tags.md)
|
||||
- [Adding rainbow bracket queries](./guides/rainbow_bracket_queries.md)
|
||||
- [Adding Languages](./guides/adding_languages.md)
|
||||
- [Adding Textobject Queries](./guides/textobject.md)
|
||||
|
@@ -1,208 +0,0 @@
|
||||
## Building from source
|
||||
|
||||
- [Configuring Helix's runtime files](#configuring-helixs-runtime-files)
|
||||
- [Linux and macOS](#linux-and-macos)
|
||||
- [Windows](#windows)
|
||||
- [Multiple runtime directories](#multiple-runtime-directories)
|
||||
- [Note to packagers](#note-to-packagers)
|
||||
- [Validating the installation](#validating-the-installation)
|
||||
- [Configure the desktop shortcut](#configure-the-desktop-shortcut)
|
||||
- [Building the Debian package](#building-the-debian-package)
|
||||
|
||||
Requirements:
|
||||
|
||||
Clone the Helix GitHub repository into a directory of your choice. The
|
||||
examples in this documentation assume installation into either `~/src/` on
|
||||
Linux and macOS, or `%userprofile%\src\` on Windows.
|
||||
|
||||
- The [Rust toolchain](https://www.rust-lang.org/tools/install)
|
||||
- The [Git version control system](https://git-scm.com/)
|
||||
- A C++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
|
||||
|
||||
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:
|
||||
|
||||
```sh
|
||||
RUSTFLAGS="-C target-feature=-crt-static"
|
||||
```
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/helix-editor/helix
|
||||
cd helix
|
||||
```
|
||||
|
||||
2. Compile from source:
|
||||
|
||||
```sh
|
||||
# Reproducible
|
||||
cargo install --path helix-term --locked
|
||||
```
|
||||
```sh
|
||||
# Optimized
|
||||
cargo install \
|
||||
--profile opt \
|
||||
--config 'build.rustflags=["-C", "target-cpu=native"]' \
|
||||
--path helix-term \
|
||||
--locked
|
||||
```
|
||||
|
||||
Either command will create the `hx` executable and construct the tree-sitter
|
||||
grammars in the local `runtime` folder.
|
||||
|
||||
> 💡 If you do not want to fetch or build grammars, set an environment variable `HELIX_DISABLE_AUTO_GRAMMAR_BUILD`
|
||||
|
||||
> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
|
||||
> grammars with `hx --grammar fetch` and compile them with
|
||||
> `hx --grammar build`. This will install them in
|
||||
> the `runtime` directory within the user's helix config directory (more
|
||||
> [details below](#multiple-runtime-directories)).
|
||||
|
||||
### Configuring Helix's runtime files
|
||||
|
||||
#### Linux and macOS
|
||||
|
||||
The **runtime** directory is one below the Helix source, so either export a
|
||||
`HELIX_RUNTIME` environment variable to point to that directory and add it to
|
||||
your `~/.bashrc` or equivalent:
|
||||
|
||||
```sh
|
||||
export HELIX_RUNTIME=~/src/helix/runtime
|
||||
```
|
||||
|
||||
Or, create a symbolic link:
|
||||
|
||||
```sh
|
||||
ln -Tsf $PWD/runtime ~/.config/helix/runtime
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
|
||||
`Edit environment variables for your account`) or use the `setx` command in
|
||||
Cmd:
|
||||
|
||||
```sh
|
||||
setx HELIX_RUNTIME "%userprofile%\src\helix\runtime"
|
||||
```
|
||||
|
||||
> 💡 `%userprofile%` resolves to your user directory like
|
||||
> `C:\Users\Your-Name\` for example.
|
||||
|
||||
Or, create a symlink in `%appdata%\helix\` that links to the source code directory:
|
||||
|
||||
| Method | Command |
|
||||
| ---------- | -------------------------------------------------------------------------------------- |
|
||||
| PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` |
|
||||
| Cmd | `cd %appdata%\helix` <br/> `mklink /D runtime "%userprofile%\src\helix\runtime"` |
|
||||
|
||||
> 💡 On Windows, creating a symbolic link may require running PowerShell or
|
||||
> Cmd as an administrator.
|
||||
|
||||
#### Multiple runtime directories
|
||||
|
||||
When Helix finds multiple runtime directories it will search through them for files in the
|
||||
following order:
|
||||
|
||||
1. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
|
||||
developing and testing helix only).
|
||||
2. `runtime/` subdirectory of OS-dependent helix user config directory.
|
||||
3. `$HELIX_RUNTIME`
|
||||
4. Distribution-specific fallback directory (set at compile time—not run time—
|
||||
with the `HELIX_DEFAULT_RUNTIME` environment variable)
|
||||
5. `runtime/` subdirectory of path to Helix executable.
|
||||
|
||||
This order also sets the priority for selecting which file will be used if multiple runtime
|
||||
directories have files with the same name.
|
||||
|
||||
#### Note to packagers
|
||||
|
||||
If you are making a package of Helix for end users, to provide a good out of
|
||||
the box experience, you should set the `HELIX_DEFAULT_RUNTIME` environment
|
||||
variable at build time (before invoking `cargo build`) to a directory which
|
||||
will store the final runtime files after installation. For example, say you want
|
||||
to package the runtime into `/usr/lib/helix/runtime`. The rough steps a build
|
||||
script could follow are:
|
||||
|
||||
1. `export HELIX_DEFAULT_RUNTIME=/usr/lib/helix/runtime`
|
||||
1. `cargo build --profile opt --locked`
|
||||
1. `cp -r runtime $BUILD_DIR/usr/lib/helix/`
|
||||
1. `cp target/opt/hx $BUILD_DIR/usr/bin/hx`
|
||||
|
||||
This way the resulting `hx` binary will always look for its runtime directory in
|
||||
`/usr/lib/helix/runtime` if the user has no custom runtime in `~/.config/helix`
|
||||
or `HELIX_RUNTIME`.
|
||||
|
||||
### Validating the installation
|
||||
|
||||
To make sure everything is set up as expected you should run the Helix health
|
||||
check:
|
||||
|
||||
```sh
|
||||
hx --health
|
||||
```
|
||||
|
||||
For more information on the health check results refer to
|
||||
[Health check](https://github.com/helix-editor/helix/wiki/Healthcheck).
|
||||
|
||||
### Configure the desktop shortcut
|
||||
|
||||
If your desktop environment supports the
|
||||
[XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html)
|
||||
you can configure Helix to show up in the application menu by copying the
|
||||
provided `.desktop` and icon files to their correct folders:
|
||||
|
||||
```sh
|
||||
cp contrib/Helix.desktop ~/.local/share/applications
|
||||
cp contrib/helix.png ~/.icons # or ~/.local/share/icons
|
||||
```
|
||||
It is recommended to convert the links in the `.desktop` file to absolute paths to avoid potential problems:
|
||||
|
||||
```sh
|
||||
sed -i -e "s|Exec=hx %F|Exec=$(readlink -f ~/.cargo/bin/hx) %F|g" \
|
||||
-e "s|Icon=helix|Icon=$(readlink -f ~/.icons/helix.png)|g" ~/.local/share/applications/Helix.desktop
|
||||
```
|
||||
|
||||
To use another terminal than the system default, you can modify the `.desktop`
|
||||
file. For example, to use `kitty`:
|
||||
|
||||
```sh
|
||||
sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
|
||||
sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
|
||||
```
|
||||
|
||||
### Building the Debian package
|
||||
|
||||
If the `.deb` file provided on the release page uses a `libc` version higher
|
||||
than that used by your Debian, Ubuntu, or Mint system, you can build the package
|
||||
from source to match your system's dependencies.
|
||||
|
||||
Install `cargo-deb`, the tool used for building the `.deb` file:
|
||||
|
||||
```sh
|
||||
cargo install cargo-deb
|
||||
```
|
||||
|
||||
After cloning and entering the Helix repository as previously described,
|
||||
use the following command to build the release binary and package it into a `.deb` file in a single step.
|
||||
|
||||
```sh
|
||||
cargo deb -- --locked
|
||||
```
|
||||
|
||||
> 💡 This locks you into the `--release` profile. But you can also build helix in any way you like.
|
||||
> As long as you leave a `target/release/hx` file, it will get packaged with `cargo deb --no-build`
|
||||
|
||||
> 💡 Don't worry about the following:
|
||||
> ```
|
||||
> warning: Failed to find dependency specification
|
||||
> ```
|
||||
> Cargo deb just reports which packaged files it didn't derive dependencies for. But
|
||||
> so far the dependency deriving seams very good, even if some of the grammar files are skipped.
|
||||
|
||||
You can find the resulted `.deb` in `target/debian/`. It should contain everything it needs, including the
|
||||
|
||||
- completions for bash, fish, zsh
|
||||
- .desktop file
|
||||
- icon (though desktop environments might use their own since the name of the package is correctly `helix`)
|
||||
- launcher to the binary with the runtime
|
@@ -1,88 +0,0 @@
|
||||
# Command line
|
||||
|
||||
- [Quoting](#quoting)
|
||||
- [Flags](#flags)
|
||||
- [Expansions](#expansions)
|
||||
- [Exceptions](#exceptions)
|
||||
|
||||
The command line is used for executing [typable commands](./commands.md#typable-commands) like `:write` or `:quit`. Press `:` to activate the command line.
|
||||
|
||||
Typable commands optionally accept arguments. `:write` for example accepts an optional path to write the file contents. The command line also supports a quoting syntax for arguments, flags to modify command behaviors, and _expansions_ - a way to insert values from the editor. Most commands support these features but some have custom parsing rules (see the [exceptions](#exceptions) below).
|
||||
|
||||
## Quoting
|
||||
|
||||
By default, command arguments are split on tabs and space characters. `:open README.md CHANGELOG.md` for example should open two files, `README.md` and `CHANGELOG.md`. Arguments that contain spaces can be surrounded in single quotes (`'`) or backticks (`` ` ``) to prevent the space from separating the argument, like `:open 'a b.txt'`.
|
||||
|
||||
Double quotes may be used the same way, but double quotes _expand_ their inner content. `:echo "%{cursor_line}"` for example may print `1` because of the expansion for the `cursor_line` variable. `:echo '%{cursor_line}'` though prints `%{cursor_line}` literally: content within single quotes or backticks is interpreted as-is.
|
||||
|
||||
On Unix systems the backslash character may be used to escape certain characters depending on where it is used. Within an argument which isn't surround in quotes, the backslash can be used to escape the space or tab characters: `:open a\ b.txt` is equivalent to `:open 'a b.txt'`. The backslash may also be used to escape quote characters (`'`, `` ` ``, `"`) or the percent token (`%`) when used at the beginning of an argument. `:echo \%%sh{foo}` for example prints `%sh{foo}` instead of invoking a `foo` shell command and `:echo \"quote` prints `"quote`. The backslash character is treated literally in any other situation on Unix systems and always on Windows: `:echo \n` always prints `\n`.
|
||||
|
||||
## Flags
|
||||
|
||||
Command flags are optional switches that can be used to alter the behavior of a command. For example the `:sort` command accepts an optional `--reverse` (or `-r` for short) flag which causes the sort command to reverse the sorting direction. Typing the `-` character shows completions for the current command's flags, if any.
|
||||
|
||||
The `--` flag specifies the end of flags. All arguments after `--` are treated as positional arguments: `:open -- -a.txt` opens a file called `-a.txt`.
|
||||
|
||||
## Expansions
|
||||
|
||||
Expansions are patterns that Helix recognizes and replaces within the command line. Helix recognizes anything starting with a percent token (`%`) as an expansion, for example `%sh{echo hi!}`. Expansions are particularly useful when used in commands like `:echo` or `:noop` for executing simple scripts. For example:
|
||||
|
||||
```toml
|
||||
[keys.normal]
|
||||
# Print the current line's git blame information to the statusline.
|
||||
space.B = ":echo %sh{git blame -L %{cursor_line},+1 %{buffer_name}}"
|
||||
```
|
||||
|
||||
Expansions take the form `%[<kind>]<open><contents><close>`. In `%sh{echo hi!}`, for example, the kind is `sh` - the shell expansion - and the contents are "echo hi!", with `{` and `}` acting as opening and closing delimiters. The following open/close characters are recognized as expansion delimiter pairs: `(`/`)`, `[`/`]`, `{`/`}` and `<`/`>`. Plus the single characters `'`, `"` or `|` may be used instead: `%{cursor_line}` is equivalent to `%<cursor_line>`, `%[cursor_line]` or `%|cursor_line|`.
|
||||
|
||||
To escape a percent character instead of treating it as an expansion, use two percent characters consecutively. To execute a shell command like `date -u +'%Y-%m-%d'`, double the percent characters: `:echo %sh{date -u +'%%Y-%%m-%%d'}`.
|
||||
|
||||
When no `<kind>` is provided, Helix will expand a **variable**. For example `%{cursor_line}` can be used as in argument to insert the line number. `:echo %{cursor_line}` for instance may print `1` to the statusline.
|
||||
|
||||
The following variables are supported:
|
||||
|
||||
| Name | Description |
|
||||
|--- |--- |
|
||||
| `cursor_line` | The line number of the primary cursor in the currently focused document, starting at 1. |
|
||||
| `cursor_column` | The column number of the primary cursor in the currently focused document, starting at 1. This is counted as the number of grapheme clusters from the start of the line rather than bytes or codepoints. |
|
||||
| `buffer_name` | The relative path of the currently focused document. `[scratch]` is expanded instead for scratch buffers. |
|
||||
| `line_ending` | A string containing the line ending of the currently focused document. For example on Unix systems this is usually a line-feed character (`\n`) but on Windows systems this may be a carriage-return plus a line-feed (`\r\n`). The line ending kind of the currently focused document can be inspected with the `:line-ending` command. |
|
||||
| `current_working_directory` | Current working directory |
|
||||
| `workspace_directory` | Nearest ancestor directory of the current working directory that contains `.git`, `.svn`, `jj` or `.helix` |
|
||||
| `language` | A string containing the language name of the currently focused document.|
|
||||
| `selection` | A string containing the contents of the primary selection of the currently focused document. |
|
||||
| `selection_line_start` | The line number of the start of the primary selection in the currently focused document, starting at 1. |
|
||||
| `selection_line_end` | The line number of the end of the primary selection in the currently focused document, starting at 1. |
|
||||
|
||||
Aside from editor variables, the following expansions may be used:
|
||||
|
||||
* Unicode `%u{..}`. The contents may contain up to six hexadecimal numbers corresponding to a Unicode codepoint value. For example `:echo %u{25CF}` prints `●` to the statusline.
|
||||
* Shell `%sh{..}`. The contents are passed to the configured shell command. For example `:echo %sh{echo "20 * 5" | bc}` may print `100` on the statusline on when using a shell with `echo` and the `bc` calculator installed. Shell expansions are evaluated recursively. `%sh{echo '%{buffer_name}:%{cursor_line}'}` for example executes a command like `echo 'README.md:1'`: the variables within the `%sh{..}` expansion are evaluated before executing the shell command.
|
||||
|
||||
As mentioned above, double quotes can be used to surround arguments containing spaces but also support expansions within the quoted content unlike singe quotes or backticks. For example `:echo "circle: %u{25CF}"` prints `circle: ●` to the statusline while `:echo 'circle: %u{25CF}'` prints `circle: %u{25CF}`.
|
||||
|
||||
Note that expansions are only evaluated once the Enter key is pressed in command mode.
|
||||
|
||||
## Exceptions
|
||||
|
||||
The following commands support expansions but otherwise pass the given argument directly to the shell program without interpreting quotes:
|
||||
|
||||
* `:insert-output`
|
||||
* `:append-output`
|
||||
* `:pipe`
|
||||
* `:pipe-to`
|
||||
* `:run-shell-command`
|
||||
|
||||
For example executing `:sh echo "%{buffer_name}:%{cursor_column}"` would pass text like `echo "README.md:1"` as an argument to the shell program: the expansions are evaluated but not the quotes. As mentioned above, percent characters can be used in shell commands by doubling the percent character. To insert the output of a command like `date -u +'%Y-%m-%d'` use `:insert-output date -u +'%%Y-%%m-%%d'`.
|
||||
|
||||
The `:set-option` and `:toggle-option` commands use regular parsing for the first argument - the config option name - and parse the rest depending on the config option's type. `:set-option` interprets the second argument as a string for string config options and parses everything else as JSON.
|
||||
|
||||
`:toggle-option`'s behavior depends on the JSON type of the config option supplied as the first argument:
|
||||
|
||||
* Booleans: only the config option name should be provided. For example `:toggle-option auto-format` will flip the `auto-format` option.
|
||||
* Strings: the rest of the command line is parsed with regular quoting rules. For example `:toggle-option indent-heuristic hybrid tree-sitter simple` cycles through "hybrid", "tree-sitter" and "simple" values on each invocation of the command.
|
||||
* Numbers, arrays and objects: the rest of the command line is parsed as a stream of JSON values. For example `:toggle-option rulers [81] [51, 73]` cycles through `[81]` and `[51, 73]`.
|
||||
|
||||
When providing multiple values to `:toggle-option` there should be no duplicates. `:toggle-option indent-heuristic hybrid simple tree-sitter simple` for example would only toggle between "hybrid" and "tree-sitter" values.
|
||||
|
||||
`:lsp-workspace-command` works similarly to `:toggle-option`. The first argument (if present) is parsed according to normal rules. The rest of the line is parsed as JSON values. Unlike `:toggle-option`, string arguments for a command must be quoted. For example `:lsp-workspace-command lsp.Command "foo" "bar"`.
|
@@ -1,16 +1,5 @@
|
||||
# Commands
|
||||
|
||||
- [Typable commands](#typable-commands)
|
||||
- [Static commands](#static-commands)
|
||||
|
||||
## Typable commands
|
||||
|
||||
Typable commands are used from command mode and may take arguments. Command mode can be activated by pressing `:`. The built-in typable commands are:
|
||||
Command mode can be activated by pressing `:`, similar to vim. Built-in commands:
|
||||
|
||||
{{#include ./generated/typable-cmd.md}}
|
||||
|
||||
## Static Commands
|
||||
|
||||
Static commands take no arguments and can be bound to keys. Static commands can also be executed from the command picker (`<space>?`). The built-in static commands are:
|
||||
|
||||
{{#include ./generated/static-cmd.md}}
|
||||
|
@@ -2,10 +2,10 @@
|
||||
|
||||
To override global configuration parameters, create a `config.toml` file located in your config directory:
|
||||
|
||||
- Linux and Mac: `~/.config/helix/config.toml`
|
||||
- Windows: `%AppData%\helix\config.toml`
|
||||
* Linux and Mac: `~/.config/helix/config.toml`
|
||||
* Windows: `%AppData%\helix\config.toml`
|
||||
|
||||
> 💡 You can easily open the config file by typing `:config-open` within Helix normal mode.
|
||||
> Note: You may use `hx --edit-config` to create and edit the `config.toml` file.
|
||||
|
||||
Example config:
|
||||
|
||||
@@ -25,11 +25,114 @@ select = "underline"
|
||||
hidden = false
|
||||
```
|
||||
|
||||
You can use a custom configuration file by specifying it with the `-c` or
|
||||
`--config` command line argument, for example `hx -c path/to/custom-config.toml`.
|
||||
You can reload the config file by issuing the `:config-reload` command. Alternatively, on Unix operating systems, you can reload it by sending the USR1
|
||||
signal to the Helix process, such as by using the command `pkill -USR1 hx`.
|
||||
## Editor
|
||||
|
||||
Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
|
||||
Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.
|
||||
### `[editor]` Section
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `3` |
|
||||
| `mouse` | Enable mouse mode. | `true` |
|
||||
| `middle-click-paste` | Middle click paste support. | `true` |
|
||||
| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
|
||||
| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
|
||||
| `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` |
|
||||
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
|
||||
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
|
||||
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
|
||||
| `auto-info` | Whether to display infoboxes | `true` |
|
||||
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` |
|
||||
|
||||
### `[editor.cursor-shape]` Section
|
||||
|
||||
Defines the shape of cursor in each mode. Note that due to limitations
|
||||
of the terminal environment, only the primary cursor can change shape.
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | ----------- | ------- |
|
||||
| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
|
||||
| `insert` | Cursor shape in [insert mode][insert mode] | `block` |
|
||||
| `select` | Cursor shape in [select mode][select mode] | `block` |
|
||||
|
||||
[normal mode]: ./keymap.md#normal-mode
|
||||
[insert mode]: ./keymap.md#insert-mode
|
||||
[select mode]: ./keymap.md#select--extend-mode
|
||||
|
||||
### `[editor.file-picker]` Section
|
||||
|
||||
Sets options for file picker and global search. All but the last key listed in
|
||||
the default file-picker configuration below are IgnoreOptions: whether hidden
|
||||
files and files listed within ignore files are ignored by (not visible in) the
|
||||
helix file picker and global search. There is also one other key, `max-depth`
|
||||
available, which is not defined by default.
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
|`hidden` | Enables ignoring hidden files. | true
|
||||
|`parents` | Enables reading ignore files from parent directories. | true
|
||||
|`ignore` | Enables reading `.ignore` files. | true
|
||||
|`git-ignore` | Enables reading `.gitignore` files. | true
|
||||
|`git-global` | Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. | true
|
||||
|`git-exclude` | Enables reading `.git/info/exclude` files. | true
|
||||
|`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`.
|
||||
|
||||
### `[editor.auto-pairs]` Section
|
||||
|
||||
Enable automatic insertion of pairs to parentheses, brackets, etc. Can be
|
||||
a simple boolean value, or a specific mapping of pairs of single characters.
|
||||
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `false` | Completely disable auto pairing, regardless of language-specific settings
|
||||
| `true` | Use the default pairs: <code>(){}[]''""``</code>
|
||||
| Mapping of pairs | e.g. `{ "(" = ")", "{" = "}", ... }`
|
||||
|
||||
Example
|
||||
|
||||
```toml
|
||||
[editor.auto-pairs]
|
||||
'(' = ')'
|
||||
'{' = '}'
|
||||
'[' = ']'
|
||||
'"' = '"'
|
||||
'`' = '`'
|
||||
'<' = '>'
|
||||
```
|
||||
|
||||
Additionally, this setting can be used in a language config. Unless
|
||||
the editor setting is `false`, this will override the editor config in
|
||||
documents with this language.
|
||||
|
||||
Example `languages.toml` that adds <> and removes ''
|
||||
|
||||
```toml
|
||||
[[language]]
|
||||
name = "rust"
|
||||
|
||||
[language.auto-pairs]
|
||||
'(' = ')'
|
||||
'{' = '}'
|
||||
'[' = ']'
|
||||
'"' = '"'
|
||||
'`' = '`'
|
||||
'<' = '>'
|
||||
```
|
||||
|
||||
### `[editor.search]` Section
|
||||
|
||||
Search specific options.
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
| `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` |
|
||||
| `wrap-around`| Whether the search should wrap after depleting the matches | `true` |
|
||||
|
||||
|
||||
## LSP
|
||||
|
||||
To display all language server messages in the status line add the following to your `config.toml`:
|
||||
|
||||
```toml
|
||||
[lsp]
|
||||
display-messages = true
|
||||
```
|
||||
|
@@ -1,3 +0,0 @@
|
||||
# Ecosystem
|
||||
|
||||
This section has information related to the wider Helix ecosystem.
|
@@ -1,510 +0,0 @@
|
||||
## Editor
|
||||
|
||||
- [`[editor]` Section](#editor-section)
|
||||
- [`[editor.clipboard-provider]` Section](#editorclipboard-provider-section)
|
||||
- [`[editor.statusline]` Section](#editorstatusline-section)
|
||||
- [`[editor.lsp]` Section](#editorlsp-section)
|
||||
- [`[editor.cursor-shape]` Section](#editorcursor-shape-section)
|
||||
- [`[editor.file-picker]` Section](#editorfile-picker-section)
|
||||
- [`[editor.auto-pairs]` Section](#editorauto-pairs-section)
|
||||
- [`[editor.auto-save]` Section](#editorauto-save-section)
|
||||
- [`[editor.search]` Section](#editorsearch-section)
|
||||
- [`[editor.whitespace]` Section](#editorwhitespace-section)
|
||||
- [`[editor.indent-guides]` Section](#editorindent-guides-section)
|
||||
- [`[editor.gutters]` Section](#editorgutters-section)
|
||||
- [`[editor.gutters.line-numbers]` Section](#editorguttersline-numbers-section)
|
||||
- [`[editor.gutters.diagnostics]` Section](#editorguttersdiagnostics-section)
|
||||
- [`[editor.gutters.diff]` Section](#editorguttersdiff-section)
|
||||
- [`[editor.gutters.spacer]` Section](#editorguttersspacer-section)
|
||||
- [`[editor.soft-wrap]` Section](#editorsoft-wrap-section)
|
||||
- [`[editor.smart-tab]` Section](#editorsmart-tab-section)
|
||||
- [`[editor.inline-diagnostics]` Section](#editorinline-diagnostics-section)
|
||||
- [`[editor.word-completion]` Section](#editorword-completion-section)
|
||||
|
||||
### `[editor]` Section
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling | `5` |
|
||||
| `mouse` | Enable mouse mode | `true` |
|
||||
| `default-yank-register` | Default register used for yank/paste | `"` |
|
||||
| `middle-click-paste` | Middle click paste support | `true` |
|
||||
| `scroll-lines` | Number of lines to scroll per scroll wheel step | `3` |
|
||||
| `shell` | Shell to use when running external commands | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
|
||||
| `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` |
|
||||
| `cursorline` | Highlight all lines with a cursor | `false` |
|
||||
| `cursorcolumn` | Highlight all columns with a cursor | `false` |
|
||||
| `continue-comments` | if helix should automatically add a line comment token if you create a new line inside a comment. | `true` |
|
||||
| `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"]` |
|
||||
| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
|
||||
| `path-completion` | Enable filepath completion. Show files and directories if an existing path at the cursor was recognized, either absolute or relative to the current opened document or current working directory (if the buffer is not yet saved). Defaults to true. | `true` |
|
||||
| `auto-format` | Enable automatic formatting on save | `true` |
|
||||
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. | `250` |
|
||||
| `completion-timeout` | Time in milliseconds after typing a word character before completions are shown, set to 5 for instant. | `250` |
|
||||
| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
|
||||
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
|
||||
| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
|
||||
| `auto-info` | Whether to display info boxes | `true` |
|
||||
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` |
|
||||
| `undercurl` | Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative | `false` |
|
||||
| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
|
||||
| `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` |
|
||||
| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
|
||||
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
|
||||
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
|
||||
| `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` |
|
||||
| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
|
||||
| `atomic-save` | Whether to use atomic operations to write documents to disk. This prevents data loss if the editor is interrupted while writing the file, but may confuse some file watching/hot reloading programs. | `true` |
|
||||
| `trim-final-newlines` | Whether to automatically remove line-endings after the final one on write | `false` |
|
||||
| `trim-trailing-whitespace` | Whether to automatically remove whitespace preceding line endings on write | `false` |
|
||||
| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
|
||||
| `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`
|
||||
| `jump-label-alphabet` | The characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first. | `"abcdefghijklmnopqrstuvwxyz"`
|
||||
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | `"hint"`
|
||||
| `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. |
|
||||
| `editor-config` | Whether to read settings from [EditorConfig](https://editorconfig.org) files | `true` |
|
||||
| `rainbow-brackets` | Whether to render rainbow colors for matching brackets. Requires tree-sitter `rainbows.scm` queries for the language. | `false` |
|
||||
| `kitty-keyboard-protocol` | Whether to enable Kitty Keyboard Protocol. Can be `enabled`, `disabled` or `auto` | `auto` |
|
||||
|
||||
### `[editor.clipboard-provider]` Section
|
||||
|
||||
Helix can be configured either to use a builtin clipboard configuration or to use
|
||||
a provided command.
|
||||
|
||||
For instance, setting it to use OSC 52 termcodes, the configuration would be:
|
||||
```toml
|
||||
[editor]
|
||||
clipboard-provider = "termcode"
|
||||
```
|
||||
|
||||
Alternatively, Helix can be configured to use arbitrary commands for clipboard integration:
|
||||
|
||||
```toml
|
||||
[editor.clipboard-provider.custom]
|
||||
yank = { command = "cat", args = ["test.txt"] }
|
||||
paste = { command = "tee", args = ["test.txt"] }
|
||||
primary-yank = { command = "cat", args = ["test-primary.txt"] } # optional
|
||||
primary-paste = { command = "tee", args = ["test-primary.txt"] } # optional
|
||||
```
|
||||
|
||||
For custom commands the contents of the yank/paste is communicated over stdin/stdout.
|
||||
|
||||
### `[editor.statusline]` Section
|
||||
|
||||
Allows configuring the statusline at the bottom of the editor.
|
||||
|
||||
The configuration distinguishes between three areas of the status line:
|
||||
|
||||
`[ ... ... LEFT ... ... | ... ... ... CENTER ... ... ... | ... ... RIGHT ... ... ]`
|
||||
|
||||
Statusline elements can be defined as follows:
|
||||
|
||||
```toml
|
||||
[editor.statusline]
|
||||
left = ["mode", "spinner"]
|
||||
center = ["file-name"]
|
||||
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
|
||||
separator = "│"
|
||||
mode.normal = "NORMAL"
|
||||
mode.insert = "INSERT"
|
||||
mode.select = "SELECT"
|
||||
diagnostics = ["warning", "error"]
|
||||
workspace-diagnostics = ["warning", "error"]
|
||||
```
|
||||
The `[editor.statusline]` key takes the following sub-keys:
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
|
||||
| `center` | A list of elements aligned to the middle of the statusline | `[]` |
|
||||
| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
|
||||
| `separator` | The character used to separate elements in the statusline | `"│"` |
|
||||
| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
|
||||
| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
|
||||
| `mode.select` | The text shown in the `mode` element for select mode | `"SEL"` |
|
||||
| `diagnostics` | A list of severities which are displayed for the current buffer | `["warning", "error"]` |
|
||||
| `workspace-diagnostics` | A list of severities which are displayed for the workspace | `["warning", "error"]` |
|
||||
|
||||
The following statusline elements can be configured:
|
||||
|
||||
| Key | Description |
|
||||
| ------ | ----------- |
|
||||
| `mode` | The current editor mode (`mode.normal`/`mode.insert`/`mode.select`) |
|
||||
| `spinner` | A progress spinner indicating LSP activity |
|
||||
| `file-name` | The path/name of the opened file |
|
||||
| `file-absolute-path` | The absolute path/name of the opened file |
|
||||
| `file-base-name` | The basename of the opened file |
|
||||
| `current-working-directory` | The current working directory |
|
||||
| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
|
||||
| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
|
||||
| `file-line-ending` | The file line endings (CRLF or LF) |
|
||||
| `file-indent-style` | The file indentation style |
|
||||
| `read-only-indicator` | An indicator that shows `[readonly]` when a file cannot be written |
|
||||
| `total-line-numbers` | The total line numbers of the opened file |
|
||||
| `file-type` | The type of the opened file |
|
||||
| `diagnostics` | The number of warnings and/or errors |
|
||||
| `workspace-diagnostics` | The number of warnings and/or errors on workspace |
|
||||
| `selections` | The primary selection index out of the number of active selections |
|
||||
| `primary-selection-length` | The number of characters currently in primary selection |
|
||||
| `position` | The cursor position |
|
||||
| `position-percentage` | The cursor position as a percentage of the total number of lines |
|
||||
| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
|
||||
| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
|
||||
| `version-control` | The current branch name or detached commit hash of the opened workspace |
|
||||
| `register` | The current selected register |
|
||||
|
||||
### `[editor.lsp]` Section
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | ----------- | ------- |
|
||||
| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
|
||||
| `display-messages` | Display LSP `window/showMessage` messages below statusline[^1] | `true` |
|
||||
| `display-progress-messages` | Display LSP progress messages below statusline[^1] | `false` |
|
||||
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
|
||||
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
|
||||
| `inlay-hints-length-limit` | Maximum displayed length (non-zero number) of inlay hints | Unset by default |
|
||||
| `display-color-swatches` | Show color swatches next to colors | `true` |
|
||||
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
|
||||
| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
|
||||
| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
|
||||
|
||||
[^1]: By default, a progress spinner is shown in the statusline beside the file path.
|
||||
|
||||
[^2]: You may also have to activate them in the language server 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!
|
||||
|
||||
### `[editor.cursor-shape]` Section
|
||||
|
||||
Defines the shape of cursor in each mode.
|
||||
Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
|
||||
|
||||
> 💡 Due to limitations of the terminal environment, only the primary cursor can
|
||||
> change shape.
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | ----------- | ------- |
|
||||
| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
|
||||
| `insert` | Cursor shape in [insert mode][insert mode] | `block` |
|
||||
| `select` | Cursor shape in [select mode][select mode] | `block` |
|
||||
|
||||
[normal mode]: ./keymap.md#normal-mode
|
||||
[insert mode]: ./keymap.md#insert-mode
|
||||
[select mode]: ./keymap.md#select--extend-mode
|
||||
|
||||
### `[editor.file-picker]` Section
|
||||
|
||||
Set options for file picker and global search. Ignoring a file means it is
|
||||
not visible in the Helix file picker and global search.
|
||||
|
||||
All git related options are only enabled in a git repository.
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
|`hidden` | Enables ignoring hidden files | `true`
|
||||
|`follow-symlinks` | Follow symlinks instead of ignoring them | `true`
|
||||
|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | `true`
|
||||
|`parents` | Enables reading ignore files from parent directories | `true`
|
||||
|`ignore` | Enables reading `.ignore` files | `true`
|
||||
|`git-ignore` | Enables reading `.gitignore` files | `true`
|
||||
|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludesfile` option | `true`
|
||||
|`git-exclude` | Enables reading `.git/info/exclude` files | `true`
|
||||
|`max-depth` | Set with an integer value for maximum depth to recurse | Unset by default
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
- Linux and Mac: `~/.config/helix/ignore`
|
||||
- Windows: `%AppData%\helix\ignore`
|
||||
|
||||
Example:
|
||||
|
||||
```ini
|
||||
# unignore in file picker and global search
|
||||
!.github/
|
||||
!.gitignore
|
||||
!.gitattributes
|
||||
```
|
||||
|
||||
### `[editor.file-explorer]` Section
|
||||
|
||||
In addition to the options for the file picker and global search, a similar set of options is presented to configure the file explorer separately. However, unlike the file picker, the defaults are set to avoid ignoring most files.
|
||||
|
||||
Note that the ignore files consulted by the file explorer when `ignore` is set to true are the same ones used by the file picker, including the aforementioned Helix-specific ignore files.
|
||||
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
|`hidden` | Enables ignoring hidden files | `false`
|
||||
|`follow-symlinks` | Follow symlinks instead of ignoring them | `false`
|
||||
|`parents` | Enables reading ignore files from parent directories | `false`
|
||||
|`ignore` | Enables reading `.ignore` files | `false`
|
||||
|`git-ignore` | Enables reading `.gitignore` files | `false`
|
||||
|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludesfile` option | `false`
|
||||
|`git-exclude` | Enables reading `.git/info/exclude` files | `false`
|
||||
|`flatten-dirs` | Enables flattening single child directories | `true`
|
||||
|
||||
|
||||
### `[editor.auto-pairs]` Section
|
||||
|
||||
Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
|
||||
simple boolean value, or a specific mapping of pairs of single characters.
|
||||
|
||||
To disable auto-pairs altogether, set `auto-pairs` to `false`:
|
||||
|
||||
```toml
|
||||
[editor]
|
||||
auto-pairs = false # defaults to `true`
|
||||
```
|
||||
|
||||
The default pairs are <code>(){}[]''""``</code>, but these can be customized by
|
||||
setting `auto-pairs` to a TOML table:
|
||||
|
||||
```toml
|
||||
[editor.auto-pairs]
|
||||
'(' = ')'
|
||||
'{' = '}'
|
||||
'[' = ']'
|
||||
'"' = '"'
|
||||
'`' = '`'
|
||||
'<' = '>'
|
||||
```
|
||||
|
||||
Additionally, this setting can be used in a language config. Unless
|
||||
the editor setting is `false`, this will override the editor config in
|
||||
documents with this language.
|
||||
|
||||
Example `languages.toml` that adds `<>` and removes `''`
|
||||
|
||||
```toml
|
||||
[[language]]
|
||||
name = "rust"
|
||||
|
||||
[language.auto-pairs]
|
||||
'(' = ')'
|
||||
'{' = '}'
|
||||
'[' = ']'
|
||||
'"' = '"'
|
||||
'`' = '`'
|
||||
'<' = '>'
|
||||
```
|
||||
|
||||
### `[editor.auto-save]` Section
|
||||
|
||||
Control auto save behavior.
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
| `focus-lost` | 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` |
|
||||
| `after-delay.enable` | Enable automatic saving after `auto-save.after-delay.timeout` milliseconds have passed since last edit. | `false` |
|
||||
| `after-delay.timeout` | Time in milliseconds since last edit before auto save timer triggers. | `3000` |
|
||||
|
||||
### `[editor.search]` Section
|
||||
|
||||
Search specific options.
|
||||
|
||||
| Key | Description | Default |
|
||||
|--|--|---------|
|
||||
| `smart-case` | Enable smart case regex searching (case-insensitive unless pattern contains upper case characters) | `true` |
|
||||
| `wrap-around`| Whether the search should wrap after depleting the matches | `true` |
|
||||
|
||||
### `[editor.whitespace]` Section
|
||||
|
||||
Options for rendering whitespace with visible characters. Use `:set whitespace.render all` to temporarily enable visible whitespace.
|
||||
|
||||
| Key | Description | Default |
|
||||
|-----|-------------|---------|
|
||||
| `render` | Whether to render whitespace. May either be `all` or `none`, or a table with sub-keys `space`, `nbsp`, `nnbsp`, `tab`, and `newline` | `none` |
|
||||
| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `nnbsp`, `newline` or `tabpad` | See example below |
|
||||
|
||||
Example
|
||||
|
||||
```toml
|
||||
[editor.whitespace]
|
||||
render = "all"
|
||||
# or control each character
|
||||
[editor.whitespace.render]
|
||||
space = "all"
|
||||
tab = "all"
|
||||
nbsp = "none"
|
||||
nnbsp = "none"
|
||||
newline = "none"
|
||||
|
||||
[editor.whitespace.characters]
|
||||
space = "·"
|
||||
nbsp = "⍽"
|
||||
nnbsp = "␣"
|
||||
tab = "→"
|
||||
newline = "⏎"
|
||||
tabpad = "·" # Tabs will look like "→···" (depending on tab width)
|
||||
```
|
||||
|
||||
### `[editor.indent-guides]` Section
|
||||
|
||||
Options for rendering vertical indent guides.
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `render` | Whether to render indent guides | `false` |
|
||||
| `character` | Literal character to use for rendering the indent guide | `│` |
|
||||
| `skip-levels` | Number of indent levels to skip | `0` |
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[editor.indent-guides]
|
||||
render = true
|
||||
character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
|
||||
skip-levels = 1
|
||||
```
|
||||
|
||||
### `[editor.gutters]` Section
|
||||
|
||||
For simplicity, `editor.gutters` accepts an array of gutter types, which will
|
||||
use default settings for all gutter components.
|
||||
|
||||
```toml
|
||||
[editor]
|
||||
gutters = ["diff", "diagnostics", "line-numbers", "spacer"]
|
||||
```
|
||||
|
||||
To customize the behavior of gutters, the `[editor.gutters]` section must
|
||||
be used. This section contains top level settings, as well as settings for
|
||||
specific gutter components as subsections.
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `layout` | A vector of gutters to display | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[editor.gutters]
|
||||
layout = ["diff", "diagnostics", "line-numbers", "spacer"]
|
||||
```
|
||||
|
||||
#### `[editor.gutters.line-numbers]` Section
|
||||
|
||||
Options for the line number gutter
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `min-width` | The minimum number of characters to use | `3` |
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[editor.gutters.line-numbers]
|
||||
min-width = 1
|
||||
```
|
||||
|
||||
#### `[editor.gutters.diagnostics]` Section
|
||||
|
||||
Currently unused
|
||||
|
||||
#### `[editor.gutters.diff]` Section
|
||||
|
||||
The `diff` gutter option displays colored bars indicating whether a `git` diff represents that a line was added, removed or changed.
|
||||
These colors are controlled by the theme attributes `diff.plus`, `diff.minus` and `diff.delta`.
|
||||
|
||||
Other diff providers will eventually be supported by a future plugin system.
|
||||
|
||||
There are currently no options for this section.
|
||||
|
||||
#### `[editor.gutters.spacer]` Section
|
||||
|
||||
Currently unused
|
||||
|
||||
### `[editor.soft-wrap]` Section
|
||||
|
||||
Options for soft wrapping lines that exceed the view width:
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `enable` | Whether soft wrapping is enabled. | `false` |
|
||||
| `max-wrap` | Maximum free space left at the end of the line. | `20` |
|
||||
| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line. | `40` |
|
||||
| `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` |
|
||||
| `wrap-at-text-width` | Soft wrap at `text-width` instead of using the full viewport size. | `false` |
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[editor.soft-wrap]
|
||||
enable = true
|
||||
max-wrap = 25 # increase value to reduce forced mid-word wrapping
|
||||
max-indent-retain = 0
|
||||
wrap-indicator = "" # set wrap-indicator to "" to hide it
|
||||
```
|
||||
|
||||
### `[editor.smart-tab]` Section
|
||||
|
||||
Options for navigating and editing using tab key.
|
||||
|
||||
| Key | Description | Default |
|
||||
|------------|-------------|---------|
|
||||
| `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` |
|
||||
| `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` |
|
||||
|
||||
|
||||
Due to lack of support for S-tab in some terminals, the default keybindings don't fully embrace smart-tab editing experience. If you enjoy smart-tab navigation and a terminal that supports the [Enhanced Keyboard protocol](https://github.com/helix-editor/helix/wiki/Terminal-Support#enhanced-keyboard-protocol), consider setting extra keybindings:
|
||||
|
||||
```
|
||||
[keys.normal]
|
||||
tab = "move_parent_node_end"
|
||||
S-tab = "move_parent_node_start"
|
||||
|
||||
[keys.insert]
|
||||
S-tab = "move_parent_node_start"
|
||||
|
||||
[keys.select]
|
||||
tab = "extend_parent_node_end"
|
||||
S-tab = "extend_parent_node_start"
|
||||
```
|
||||
|
||||
### `[editor.inline-diagnostics]` Section
|
||||
|
||||
Options for rendering diagnostics inside the text like shown below
|
||||
|
||||
```
|
||||
fn main() {
|
||||
let foo = bar;
|
||||
└─ no such value in this scope
|
||||
}
|
||||
````
|
||||
|
||||
| Key | Description | Default |
|
||||
|------------|-------------|---------|
|
||||
| `cursor-line` | The minimum severity that a diagnostic must have to be shown inline on the line that contains the primary cursor. Set to `disable` to not show any diagnostics inline. This option does not have any effect when in insert-mode and will only take effect 350ms after moving the cursor to a different line. | `"warning"` |
|
||||
| `other-lines` | The minimum severity that a diagnostic must have to be shown inline on a line that does not contain the cursor-line. Set to `disable` to not show any diagnostics inline. | `"disable"` |
|
||||
| `prefix-len` | How many horizontal bars `─` are rendered before the diagnostic text. | `1` |
|
||||
| `max-wrap` | Equivalent of the `editor.soft-wrap.max-wrap` option for diagnostics. | `20` |
|
||||
| `max-diagnostics` | Maximum number of diagnostics to render inline for a given line | `10` |
|
||||
|
||||
The allowed values for `cursor-line` and `other-lines` are: `error`, `warning`, `info`, `hint`.
|
||||
|
||||
The (first) diagnostic with the highest severity that is not shown inline is rendered at the end of the line (as long as its severity is higher than the `end-of-line-diagnostics` config option):
|
||||
|
||||
```
|
||||
fn main() {
|
||||
let baz = 1;
|
||||
let foo = bar; a local variable with a similar name exists: baz
|
||||
└─ no such value in this scope
|
||||
}
|
||||
```
|
||||
|
||||
### `[editor.word-completion]` Section
|
||||
|
||||
Options for controlling completion of words from open buffers.
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `enable` | Whether word completion is enabled | `true` |
|
||||
| `trigger-length` | Number of word characters to type before triggering completion | `7` |
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[editor.word-completion]
|
||||
enable = true
|
||||
# Set the trigger length lower so that words are completed more often
|
||||
trigger-length = 4
|
||||
```
|
@@ -1,12 +1,12 @@
|
||||
# Migrating from Vim
|
||||
|
||||
Helix's editing model is strongly inspired from Vim and Kakoune, and a notable
|
||||
difference from Vim (and the most striking similarity to Kakoune) is that Helix
|
||||
follows the `selection → action` model. This means that whatever you are
|
||||
going to act on (a word, a paragraph, a line, etc.) is selected first and the
|
||||
action itself (delete, change, yank, etc.) comes second. A cursor is simply a
|
||||
Helix's editing model is strongly inspired from vim and kakoune, and a notable
|
||||
difference from vim (and the most striking similarity to kakoune) is that Helix
|
||||
follows the `selection → action` model. This means that the whatever you are
|
||||
going to act on (a word, a paragraph, a line, etc) is selected first and the
|
||||
action itself (delete, change, yank, etc) comes second. A cursor is simply a
|
||||
single width selection.
|
||||
|
||||
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).
|
||||
See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim).
|
||||
|
||||
> TODO: Mention textobjects, surround, registers
|
||||
> TODO: Mention texobjects, surround, registers
|
||||
|
@@ -1,304 +1,66 @@
|
||||
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Code Navigation Tags | Rainbow Brackets | Default language servers |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| ada | ✓ | ✓ | | | | `ada_language_server` |
|
||||
| adl | ✓ | ✓ | ✓ | | | |
|
||||
| agda | ✓ | | | | | |
|
||||
| alloy | ✓ | | | | | |
|
||||
| amber | ✓ | | | | | `amber-lsp` |
|
||||
| astro | ✓ | | | | | `astro-ls` |
|
||||
| awk | ✓ | ✓ | | | | `awk-language-server` |
|
||||
| bash | ✓ | ✓ | ✓ | ✓ | ✓ | `bash-language-server` |
|
||||
| bass | ✓ | | | | | `bass` |
|
||||
| beancount | ✓ | | | | | `beancount-language-server` |
|
||||
| bibtex | ✓ | | | | | `texlab` |
|
||||
| bicep | ✓ | | | | | `bicep-langserver` |
|
||||
| bitbake | ✓ | | | | | `bitbake-language-server` |
|
||||
| blade | ✓ | ✓ | | | ✓ | |
|
||||
| blueprint | ✓ | | | | | `blueprint-compiler` |
|
||||
| c | ✓ | ✓ | ✓ | ✓ | ✓ | `clangd` |
|
||||
| c-sharp | ✓ | ✓ | | ✓ | | `OmniSharp` |
|
||||
| cabal | | | | | | `haskell-language-server-wrapper` |
|
||||
| caddyfile | ✓ | ✓ | ✓ | | | |
|
||||
| cairo | ✓ | ✓ | ✓ | | | `cairo-language-server` |
|
||||
| capnp | ✓ | | ✓ | | | |
|
||||
| cel | ✓ | | | | | |
|
||||
| circom | ✓ | | | | | `circom-lsp` |
|
||||
| clarity | ✓ | | | | | `clarinet` |
|
||||
| clojure | ✓ | | | | ✓ | `clojure-lsp` |
|
||||
| cmake | ✓ | ✓ | ✓ | | | `neocmakelsp`, `cmake-language-server` |
|
||||
| codeql | ✓ | ✓ | | | | `codeql` |
|
||||
| comment | ✓ | | | | | |
|
||||
| common-lisp | ✓ | | ✓ | | ✓ | `cl-lsp` |
|
||||
| cpon | ✓ | | ✓ | | | |
|
||||
| cpp | ✓ | ✓ | ✓ | ✓ | ✓ | `clangd` |
|
||||
| cross-config | ✓ | ✓ | | | ✓ | `taplo`, `tombi` |
|
||||
| crystal | ✓ | ✓ | ✓ | ✓ | | `crystalline`, `ameba-ls` |
|
||||
| css | ✓ | | ✓ | | ✓ | `vscode-css-language-server` |
|
||||
| csv | ✓ | | | | | |
|
||||
| cue | ✓ | | | | | `cuelsp` |
|
||||
| cylc | ✓ | ✓ | ✓ | | | |
|
||||
| cython | ✓ | | ✓ | ✓ | | |
|
||||
| d | ✓ | ✓ | ✓ | | | `serve-d` |
|
||||
| dart | ✓ | ✓ | ✓ | | | `dart` |
|
||||
| dbml | ✓ | | | | | |
|
||||
| debian | ✓ | | | | | |
|
||||
| devicetree | ✓ | | | | | `dts-lsp` |
|
||||
| dhall | ✓ | ✓ | | | | `dhall-lsp-server` |
|
||||
| diff | ✓ | | | | | |
|
||||
| djot | ✓ | | | | | |
|
||||
| docker-bake | ✓ | ✓ | ✓ | ✓ | ✓ | `docker-language-server` |
|
||||
| docker-compose | ✓ | ✓ | ✓ | | | `docker-compose-langserver`, `yaml-language-server`, `docker-language-server` |
|
||||
| dockerfile | ✓ | ✓ | | | | `docker-langserver`, `docker-language-server` |
|
||||
| dot | ✓ | | | | | `dot-language-server` |
|
||||
| doxyfile | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| dtd | ✓ | | | | | |
|
||||
| dune | ✓ | | | | | |
|
||||
| dunstrc | ✓ | | | | | |
|
||||
| earthfile | ✓ | ✓ | ✓ | | | `earthlyls` |
|
||||
| edoc | ✓ | | | | | |
|
||||
| eex | ✓ | | | | | |
|
||||
| eiffel | ✓ | ✓ | ✓ | | | `eiffel-language-server` |
|
||||
| ejs | ✓ | | | | | |
|
||||
| elisp | ✓ | | | ✓ | | |
|
||||
| elixir | ✓ | ✓ | ✓ | ✓ | ✓ | `elixir-ls`, `expert` |
|
||||
| elm | ✓ | ✓ | | ✓ | | `elm-language-server` |
|
||||
| elvish | ✓ | | | | | `elvish` |
|
||||
| env | ✓ | ✓ | | | | |
|
||||
| erb | ✓ | | | | | |
|
||||
| erlang | ✓ | ✓ | | ✓ | ✓ | `erlang_ls`, `elp` |
|
||||
| esdl | ✓ | | | | | |
|
||||
| fennel | ✓ | | | | | `fennel-ls` |
|
||||
| fga | ✓ | ✓ | ✓ | | | |
|
||||
| fidl | ✓ | | | | | |
|
||||
| fish | ✓ | ✓ | ✓ | | | `fish-lsp` |
|
||||
| flatbuffers | ✓ | | | | | |
|
||||
| forth | ✓ | | | | | `forth-lsp` |
|
||||
| fortran | ✓ | | ✓ | | | `fortls` |
|
||||
| fsharp | ✓ | | | | | `fsautocomplete` |
|
||||
| gas | ✓ | ✓ | | | | `asm-lsp` |
|
||||
| gdscript | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| gemini | ✓ | | | | | |
|
||||
| gherkin | ✓ | | | | | |
|
||||
| ghostty | ✓ | | | | | |
|
||||
| git-attributes | ✓ | | | | | |
|
||||
| git-cliff-config | ✓ | ✓ | | | ✓ | `taplo`, `tombi` |
|
||||
| git-commit | ✓ | ✓ | | | | |
|
||||
| git-config | ✓ | ✓ | | | | |
|
||||
| git-ignore | ✓ | | | | | |
|
||||
| git-notes | ✓ | | | | | |
|
||||
| git-rebase | ✓ | | | | | |
|
||||
| gitlab-ci | ✓ | ✓ | ✓ | ✓ | ✓ | `yaml-language-server`, `gitlab-ci-ls` |
|
||||
| gjs | ✓ | ✓ | ✓ | ✓ | | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
|
||||
| gleam | ✓ | ✓ | | | ✓ | `gleam` |
|
||||
| glimmer | ✓ | | | | | `ember-language-server` |
|
||||
| glsl | ✓ | ✓ | ✓ | | | `glsl_analyzer` |
|
||||
| gn | ✓ | | | | | |
|
||||
| go | ✓ | ✓ | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
|
||||
| go-format-string | ✓ | | | | ✓ | |
|
||||
| godot-resource | ✓ | ✓ | | | | |
|
||||
| gomod | ✓ | | | | | `gopls` |
|
||||
| gotmpl | ✓ | | | | | `gopls` |
|
||||
| gowork | ✓ | | | | | `gopls` |
|
||||
| gpr | ✓ | | | | | `ada_language_server` |
|
||||
| graphql | ✓ | ✓ | | | | `graphql-lsp` |
|
||||
| gren | ✓ | ✓ | | | | |
|
||||
| groovy | ✓ | | | | | |
|
||||
| gts | ✓ | ✓ | ✓ | ✓ | | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
|
||||
| hare | ✓ | | | | | |
|
||||
| haskell | ✓ | ✓ | | | | `haskell-language-server-wrapper` |
|
||||
| haskell-persistent | ✓ | | | | | |
|
||||
| hcl | ✓ | ✓ | ✓ | | | `terraform-ls` |
|
||||
| hdl | ✓ | | | | | `hdls` |
|
||||
| heex | ✓ | ✓ | | | | `elixir-ls`, `expert` |
|
||||
| helm | ✓ | | | | | `helm_ls` |
|
||||
| hocon | ✓ | ✓ | ✓ | | | |
|
||||
| hoon | ✓ | | | | | |
|
||||
| hosts | ✓ | | | | | |
|
||||
| html | ✓ | ✓ | | | ✓ | `vscode-html-language-server`, `superhtml` |
|
||||
| htmldjango | ✓ | | | | | `djlsp`, `vscode-html-language-server`, `superhtml` |
|
||||
| hurl | ✓ | ✓ | ✓ | | | |
|
||||
| hyprlang | ✓ | | ✓ | | | `hyprls` |
|
||||
| idris | | | | | | `idris2-lsp` |
|
||||
| iex | ✓ | | | | | |
|
||||
| ini | ✓ | | | | | |
|
||||
| ink | ✓ | | | | | |
|
||||
| inko | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| janet | ✓ | | ✓ | | ✓ | |
|
||||
| java | ✓ | ✓ | ✓ | ✓ | ✓ | `jdtls` |
|
||||
| javascript | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| jinja | ✓ | | | | | |
|
||||
| jjconfig | ✓ | ✓ | ✓ | | | `taplo`, `tombi` |
|
||||
| jjdescription | ✓ | | | | | |
|
||||
| jjrevset | ✓ | | | | | |
|
||||
| jjtemplate | ✓ | | | | | |
|
||||
| jq | ✓ | ✓ | | | | `jq-lsp` |
|
||||
| jsdoc | ✓ | | | | | |
|
||||
| json | ✓ | ✓ | ✓ | | ✓ | `vscode-json-language-server` |
|
||||
| json-ld | ✓ | ✓ | ✓ | | | `vscode-json-language-server` |
|
||||
| json5 | ✓ | | | | | |
|
||||
| jsonc | ✓ | | ✓ | | | `vscode-json-language-server` |
|
||||
| jsonnet | ✓ | | | | | `jsonnet-language-server` |
|
||||
| jsx | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| julia | ✓ | ✓ | ✓ | | | `julia` |
|
||||
| just | ✓ | ✓ | ✓ | ✓ | | `just-lsp` |
|
||||
| kconfig | ✓ | | ✓ | | | |
|
||||
| kdl | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| koka | ✓ | | ✓ | | | `koka` |
|
||||
| kotlin | ✓ | ✓ | ✓ | ✓ | | `kotlin-language-server` |
|
||||
| koto | ✓ | ✓ | ✓ | | ✓ | `koto-ls` |
|
||||
| latex | ✓ | ✓ | | | | `texlab` |
|
||||
| ld | ✓ | | ✓ | | | |
|
||||
| ldif | ✓ | | | | | |
|
||||
| lean | ✓ | | | | | `lake` |
|
||||
| ledger | ✓ | | | | | |
|
||||
| llvm | ✓ | ✓ | ✓ | | | |
|
||||
| llvm-mir | ✓ | ✓ | ✓ | | | |
|
||||
| llvm-mir-yaml | ✓ | | ✓ | | | |
|
||||
| log | ✓ | | | | | |
|
||||
| lpf | ✓ | | | | | |
|
||||
| lua | ✓ | ✓ | ✓ | | ✓ | `lua-language-server` |
|
||||
| luap | ✓ | | | | | |
|
||||
| luau | ✓ | ✓ | ✓ | | | `luau-lsp` |
|
||||
| mail | ✓ | ✓ | | | | |
|
||||
| make | ✓ | | ✓ | | | |
|
||||
| markdoc | ✓ | | | | | `markdoc-ls` |
|
||||
| markdown | ✓ | | | ✓ | | `marksman`, `markdown-oxide` |
|
||||
| markdown-rustdoc | ✓ | | | | | |
|
||||
| markdown.inline | ✓ | | | | | |
|
||||
| matlab | ✓ | ✓ | ✓ | | | |
|
||||
| mermaid | ✓ | | | | | |
|
||||
| meson | ✓ | | ✓ | | | `mesonlsp` |
|
||||
| mint | | | | | | `mint` |
|
||||
| miseconfig | ✓ | ✓ | ✓ | | | `taplo`, `tombi` |
|
||||
| mojo | ✓ | ✓ | ✓ | | | `pixi` |
|
||||
| move | ✓ | | | | | |
|
||||
| msbuild | ✓ | | ✓ | | | |
|
||||
| nasm | ✓ | ✓ | | | | `asm-lsp` |
|
||||
| nearley | ✓ | | | | ✓ | |
|
||||
| nestedtext | ✓ | ✓ | ✓ | | | |
|
||||
| nginx | ✓ | | | | | |
|
||||
| nickel | ✓ | | ✓ | | | `nls` |
|
||||
| nim | ✓ | ✓ | ✓ | | | `nimlangserver` |
|
||||
| nix | ✓ | ✓ | ✓ | | ✓ | `nil`, `nixd` |
|
||||
| nu | ✓ | ✓ | ✓ | | | `nu` |
|
||||
| nunjucks | ✓ | | | | | |
|
||||
| ocaml | ✓ | | ✓ | | | `ocamllsp` |
|
||||
| ocaml-interface | ✓ | | | | | `ocamllsp` |
|
||||
| odin | ✓ | ✓ | ✓ | | | `ols` |
|
||||
| ohm | ✓ | ✓ | ✓ | | | |
|
||||
| opencl | ✓ | ✓ | ✓ | | | `clangd` |
|
||||
| openscad | ✓ | | | | | `openscad-lsp` |
|
||||
| org | ✓ | | | | | |
|
||||
| pascal | ✓ | ✓ | | | | `pasls` |
|
||||
| passwd | ✓ | | | | | |
|
||||
| pem | ✓ | | | | | |
|
||||
| perl | ✓ | ✓ | ✓ | | | `perlnavigator` |
|
||||
| pest | ✓ | ✓ | ✓ | | | `pest-language-server` |
|
||||
| php | ✓ | ✓ | ✓ | ✓ | ✓ | `intelephense` |
|
||||
| php-only | ✓ | | | ✓ | | |
|
||||
| pip-requirements | ✓ | | | | | |
|
||||
| pkgbuild | ✓ | ✓ | ✓ | | | `termux-language-server`, `bash-language-server` |
|
||||
| pkl | ✓ | | ✓ | | | `pkl-lsp` |
|
||||
| po | ✓ | ✓ | | | | |
|
||||
| pod | ✓ | | | | | |
|
||||
| ponylang | ✓ | ✓ | ✓ | | | |
|
||||
| powershell | ✓ | | | | | |
|
||||
| prisma | ✓ | ✓ | | | | `prisma-language-server` |
|
||||
| prolog | ✓ | | ✓ | | | `swipl` |
|
||||
| properties | ✓ | ✓ | | | | |
|
||||
| protobuf | ✓ | ✓ | ✓ | ✓ | | `buf`, `pb`, `protols` |
|
||||
| prql | ✓ | | | | | |
|
||||
| pug | ✓ | | | | | |
|
||||
| purescript | ✓ | ✓ | | | | `purescript-language-server` |
|
||||
| python | ✓ | ✓ | ✓ | ✓ | ✓ | `ty`, `ruff`, `jedi-language-server`, `pylsp` |
|
||||
| qml | ✓ | ✓ | ✓ | | | `qmlls` |
|
||||
| quarto | ✓ | | ✓ | | | |
|
||||
| quint | ✓ | | | | | `quint-language-server` |
|
||||
| r | ✓ | | | | | `R` |
|
||||
| racket | ✓ | | ✓ | | ✓ | `racket` |
|
||||
| regex | ✓ | | | | ✓ | |
|
||||
| rego | ✓ | | | | | `regols` |
|
||||
| rescript | ✓ | ✓ | | | | `rescript-language-server` |
|
||||
| rmarkdown | ✓ | | ✓ | | | `R` |
|
||||
| robot | ✓ | | | | | `robotframework_ls` |
|
||||
| robots.txt | ✓ | ✓ | | ✓ | | |
|
||||
| ron | ✓ | | ✓ | ✓ | ✓ | |
|
||||
| rst | ✓ | | | | | |
|
||||
| ruby | ✓ | ✓ | ✓ | ✓ | ✓ | `ruby-lsp`, `solargraph` |
|
||||
| rust | ✓ | ✓ | ✓ | ✓ | ✓ | `rust-analyzer` |
|
||||
| rust-format-args | ✓ | | | | | |
|
||||
| rust-format-args-macro | ✓ | ✓ | ✓ | | ✓ | |
|
||||
| sage | ✓ | ✓ | | | | |
|
||||
| scala | ✓ | ✓ | ✓ | | | `metals` |
|
||||
| scheme | ✓ | ✓ | ✓ | | ✓ | |
|
||||
| scss | ✓ | | | | ✓ | `vscode-css-language-server` |
|
||||
| shellcheckrc | ✓ | ✓ | | | | |
|
||||
| slang | ✓ | ✓ | ✓ | | | `slangd` |
|
||||
| slint | ✓ | ✓ | ✓ | | | `slint-lsp` |
|
||||
| slisp | ✓ | | | ✓ | | |
|
||||
| smali | ✓ | | ✓ | | | |
|
||||
| smithy | ✓ | | | | | `cs` |
|
||||
| sml | ✓ | | | | | |
|
||||
| snakemake | ✓ | | ✓ | | | `pylsp` |
|
||||
| solidity | ✓ | ✓ | | | | `solc` |
|
||||
| sourcepawn | ✓ | ✓ | | | | `sourcepawn-studio` |
|
||||
| spade | ✓ | | ✓ | | | `spade-language-server` |
|
||||
| spicedb | ✓ | | | ✓ | | |
|
||||
| sql | ✓ | ✓ | | | | |
|
||||
| sshclientconfig | ✓ | | | | | |
|
||||
| starlark | ✓ | ✓ | ✓ | | ✓ | `starpls` |
|
||||
| strace | ✓ | | | | | |
|
||||
| strictdoc | ✓ | | | ✓ | | |
|
||||
| supercollider | ✓ | | | | | |
|
||||
| svelte | ✓ | | ✓ | | | `svelteserver` |
|
||||
| sway | ✓ | ✓ | ✓ | | | `forc` |
|
||||
| swift | ✓ | ✓ | | | ✓ | `sourcekit-lsp` |
|
||||
| systemd | ✓ | | | | | `systemd-lsp` |
|
||||
| systemverilog | ✓ | | | | | |
|
||||
| t32 | ✓ | | | | | |
|
||||
| tablegen | ✓ | ✓ | ✓ | | | |
|
||||
| tact | ✓ | ✓ | ✓ | | | |
|
||||
| task | ✓ | | | | | |
|
||||
| tcl | ✓ | | ✓ | | | |
|
||||
| teal | ✓ | | | | | `teal-language-server` |
|
||||
| templ | ✓ | | | | | `templ` |
|
||||
| tera | ✓ | | | | | |
|
||||
| textproto | ✓ | ✓ | ✓ | | | |
|
||||
| tfvars | ✓ | | ✓ | | | `terraform-ls` |
|
||||
| thrift | ✓ | | | | | |
|
||||
| tlaplus | ✓ | | | | | |
|
||||
| todotxt | ✓ | | | | | |
|
||||
| toml | ✓ | ✓ | | | ✓ | `taplo`, `tombi` |
|
||||
| tsq | ✓ | | | | ✓ | `ts_query_ls` |
|
||||
| tsx | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| twig | ✓ | | | | | |
|
||||
| typescript | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| typespec | ✓ | ✓ | ✓ | | | `tsp-server` |
|
||||
| typst | ✓ | | | ✓ | | `tinymist` |
|
||||
| ungrammar | ✓ | | | | | |
|
||||
| unison | ✓ | ✓ | ✓ | | | |
|
||||
| uxntal | ✓ | | | | | |
|
||||
| v | ✓ | ✓ | ✓ | | | `v-analyzer` |
|
||||
| vala | ✓ | ✓ | | | | `vala-language-server` |
|
||||
| vento | ✓ | | | | | |
|
||||
| verilog | ✓ | ✓ | | | | `svlangserver` |
|
||||
| vhdl | ✓ | | | | | `vhdl_ls` |
|
||||
| vhs | ✓ | | | | | |
|
||||
| vim | ✓ | | | | | |
|
||||
| vue | ✓ | | | | | `vue-language-server` |
|
||||
| wast | ✓ | | | | | |
|
||||
| wat | ✓ | | | | | `wat_server` |
|
||||
| webc | ✓ | | | | | |
|
||||
| werk | ✓ | | | | | |
|
||||
| wesl | ✓ | ✓ | | | | |
|
||||
| wgsl | ✓ | ✓ | ✓ | ✓ | ✓ | `wgsl-analyzer` |
|
||||
| wikitext | ✓ | | | | | `wikitext-lsp` |
|
||||
| wit | ✓ | | ✓ | | | |
|
||||
| wren | ✓ | ✓ | ✓ | | | |
|
||||
| xit | ✓ | | | | | |
|
||||
| xml | ✓ | ✓ | ✓ | | ✓ | |
|
||||
| xtc | ✓ | | | | | |
|
||||
| yaml | ✓ | ✓ | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
|
||||
| yara | ✓ | | | | | `yls` |
|
||||
| yuck | ✓ | | | | | |
|
||||
| zig | ✓ | ✓ | ✓ | | | `zls` |
|
||||
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| bash | ✓ | | | `bash-language-server` |
|
||||
| c | ✓ | ✓ | ✓ | `clangd` |
|
||||
| c-sharp | ✓ | | | `OmniSharp` |
|
||||
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
|
||||
| comment | ✓ | | | |
|
||||
| cpp | ✓ | ✓ | ✓ | `clangd` |
|
||||
| css | ✓ | | | |
|
||||
| dart | ✓ | | ✓ | `dart` |
|
||||
| dockerfile | ✓ | | | `docker-langserver` |
|
||||
| elixir | ✓ | | | `elixir-ls` |
|
||||
| elm | ✓ | | | `elm-language-server` |
|
||||
| erlang | ✓ | | | |
|
||||
| fish | ✓ | ✓ | ✓ | |
|
||||
| git-commit | ✓ | | | |
|
||||
| git-config | ✓ | | | |
|
||||
| git-diff | ✓ | | | |
|
||||
| git-rebase | ✓ | | | |
|
||||
| glsl | ✓ | | ✓ | |
|
||||
| go | ✓ | ✓ | ✓ | `gopls` |
|
||||
| graphql | ✓ | | | |
|
||||
| haskell | ✓ | | | `haskell-language-server-wrapper` |
|
||||
| hcl | ✓ | | ✓ | `terraform-ls` |
|
||||
| html | ✓ | | | |
|
||||
| iex | ✓ | | | |
|
||||
| java | ✓ | | | |
|
||||
| javascript | ✓ | | ✓ | `typescript-language-server` |
|
||||
| json | ✓ | | ✓ | |
|
||||
| julia | ✓ | | | `julia` |
|
||||
| kotlin | ✓ | | | `kotlin-language-server` |
|
||||
| latex | ✓ | | | |
|
||||
| lean | ✓ | | | `lean` |
|
||||
| ledger | ✓ | | | |
|
||||
| llvm | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir-yaml | ✓ | | ✓ | |
|
||||
| lua | ✓ | | ✓ | |
|
||||
| make | ✓ | | | |
|
||||
| markdown | ✓ | | | |
|
||||
| mint | | | | `mint` |
|
||||
| nix | ✓ | | ✓ | `rnix-lsp` |
|
||||
| ocaml | ✓ | | ✓ | |
|
||||
| ocaml-interface | ✓ | | | |
|
||||
| perl | ✓ | ✓ | ✓ | |
|
||||
| php | ✓ | ✓ | ✓ | |
|
||||
| prolog | | | | `swipl` |
|
||||
| protobuf | ✓ | | ✓ | |
|
||||
| python | ✓ | ✓ | ✓ | `pylsp` |
|
||||
| racket | | | | `racket` |
|
||||
| regex | ✓ | | | |
|
||||
| rescript | ✓ | ✓ | | `rescript-language-server` |
|
||||
| ruby | ✓ | | ✓ | `solargraph` |
|
||||
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
|
||||
| scala | ✓ | | ✓ | `metals` |
|
||||
| svelte | ✓ | | ✓ | `svelteserver` |
|
||||
| tablegen | ✓ | ✓ | ✓ | |
|
||||
| toml | ✓ | | | |
|
||||
| tsq | ✓ | | | |
|
||||
| tsx | ✓ | | | `typescript-language-server` |
|
||||
| twig | ✓ | | | |
|
||||
| typescript | ✓ | | ✓ | `typescript-language-server` |
|
||||
| vue | ✓ | | | |
|
||||
| wgsl | ✓ | | | |
|
||||
| yaml | ✓ | | ✓ | |
|
||||
| zig | ✓ | | ✓ | `zls` |
|
||||
|
@@ -1,315 +0,0 @@
|
||||
| Name | Description | Default keybinds |
|
||||
| --- | --- | --- |
|
||||
| `no_op` | Do nothing | |
|
||||
| `move_char_left` | Move left | normal: `` h ``, `` <left> ``, insert: `` <left> `` |
|
||||
| `move_char_right` | Move right | normal: `` l ``, `` <right> ``, insert: `` <right> `` |
|
||||
| `move_line_up` | Move up | normal: `` gk `` |
|
||||
| `move_line_down` | Move down | normal: `` gj `` |
|
||||
| `move_visual_line_up` | Move up | normal: `` k ``, `` <up> ``, insert: `` <up> `` |
|
||||
| `move_visual_line_down` | Move down | normal: `` j ``, `` <down> ``, insert: `` <down> `` |
|
||||
| `extend_char_left` | Extend left | select: `` h ``, `` <left> `` |
|
||||
| `extend_char_right` | Extend right | select: `` l ``, `` <right> `` |
|
||||
| `extend_line_up` | Extend up | select: `` gk `` |
|
||||
| `extend_line_down` | Extend down | select: `` gj `` |
|
||||
| `extend_visual_line_up` | Extend up | select: `` k ``, `` <up> `` |
|
||||
| `extend_visual_line_down` | Extend down | select: `` j ``, `` <down> `` |
|
||||
| `copy_selection_on_next_line` | Copy selection on next line | normal: `` C ``, select: `` C `` |
|
||||
| `copy_selection_on_prev_line` | Copy selection on previous line | normal: `` <A-C> ``, select: `` <A-C> `` |
|
||||
| `move_next_word_start` | Move to start of next word | normal: `` w `` |
|
||||
| `move_prev_word_start` | Move to start of previous word | normal: `` b `` |
|
||||
| `move_next_word_end` | Move to end of next word | normal: `` e `` |
|
||||
| `move_prev_word_end` | Move to end of previous word | |
|
||||
| `move_next_long_word_start` | Move to start of next long word | normal: `` W `` |
|
||||
| `move_prev_long_word_start` | Move to start of previous long word | normal: `` B `` |
|
||||
| `move_next_long_word_end` | Move to end of next long word | normal: `` E `` |
|
||||
| `move_prev_long_word_end` | Move to end of previous long word | |
|
||||
| `move_next_sub_word_start` | Move to start of next sub word | |
|
||||
| `move_prev_sub_word_start` | Move to start of previous sub word | |
|
||||
| `move_next_sub_word_end` | Move to end of next sub word | |
|
||||
| `move_prev_sub_word_end` | Move to end of previous sub word | |
|
||||
| `move_parent_node_end` | Move to end of the parent node | normal: `` <A-e> `` |
|
||||
| `move_parent_node_start` | Move to beginning of the parent node | normal: `` <A-b> `` |
|
||||
| `extend_next_word_start` | Extend to start of next word | select: `` w `` |
|
||||
| `extend_prev_word_start` | Extend to start of previous word | select: `` b `` |
|
||||
| `extend_next_word_end` | Extend to end of next word | select: `` e `` |
|
||||
| `extend_prev_word_end` | Extend to end of previous word | |
|
||||
| `extend_next_long_word_start` | Extend to start of next long word | select: `` W `` |
|
||||
| `extend_prev_long_word_start` | Extend to start of previous long word | select: `` B `` |
|
||||
| `extend_next_long_word_end` | Extend to end of next long word | select: `` E `` |
|
||||
| `extend_prev_long_word_end` | Extend to end of prev long word | |
|
||||
| `extend_next_sub_word_start` | Extend to start of next sub word | |
|
||||
| `extend_prev_sub_word_start` | Extend to start of previous sub word | |
|
||||
| `extend_next_sub_word_end` | Extend to end of next sub word | |
|
||||
| `extend_prev_sub_word_end` | Extend to end of prev sub word | |
|
||||
| `extend_parent_node_end` | Extend to end of the parent node | select: `` <A-e> `` |
|
||||
| `extend_parent_node_start` | Extend to beginning of the parent node | select: `` <A-b> `` |
|
||||
| `find_till_char` | Move till next occurrence of char | normal: `` t `` |
|
||||
| `find_next_char` | Move to next occurrence of char | normal: `` f `` |
|
||||
| `extend_till_char` | Extend till next occurrence of char | select: `` t `` |
|
||||
| `extend_next_char` | Extend to next occurrence of char | select: `` f `` |
|
||||
| `till_prev_char` | Move till previous occurrence of char | normal: `` T `` |
|
||||
| `find_prev_char` | Move to previous occurrence of char | normal: `` F `` |
|
||||
| `extend_till_prev_char` | Extend till previous occurrence of char | select: `` T `` |
|
||||
| `extend_prev_char` | Extend to previous occurrence of char | select: `` F `` |
|
||||
| `repeat_last_motion` | Repeat last motion | normal: `` <A-.> ``, select: `` <A-.> `` |
|
||||
| `replace` | Replace with new char | normal: `` r ``, select: `` r `` |
|
||||
| `switch_case` | Switch (toggle) case | normal: `` ~ ``, select: `` ~ `` |
|
||||
| `switch_to_uppercase` | Switch to uppercase | normal: `` <A-`> ``, select: `` <A-`> `` |
|
||||
| `switch_to_lowercase` | Switch to lowercase | normal: `` ` ``, select: `` ` `` |
|
||||
| `page_up` | Move page up | normal: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, select: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, insert: `` <pageup> `` |
|
||||
| `page_down` | Move page down | normal: `` <C-f> ``, `` Z<C-f> ``, `` z<C-f> ``, `` <pagedown> ``, `` Z<pagedown> ``, `` z<pagedown> ``, select: `` <C-f> ``, `` Z<C-f> ``, `` z<C-f> ``, `` <pagedown> ``, `` Z<pagedown> ``, `` z<pagedown> ``, insert: `` <pagedown> `` |
|
||||
| `half_page_up` | Move half page up | |
|
||||
| `half_page_down` | Move half page down | |
|
||||
| `page_cursor_up` | Move page and cursor up | |
|
||||
| `page_cursor_down` | Move page and cursor down | |
|
||||
| `page_cursor_half_up` | Move page and cursor half up | normal: `` <C-u> ``, `` Z<C-u> ``, `` z<C-u> ``, `` Z<backspace> ``, `` z<backspace> ``, select: `` <C-u> ``, `` Z<C-u> ``, `` z<C-u> ``, `` Z<backspace> ``, `` z<backspace> `` |
|
||||
| `page_cursor_half_down` | Move page and cursor half down | normal: `` <C-d> ``, `` Z<C-d> ``, `` z<C-d> ``, `` Z<space> ``, `` z<space> ``, select: `` <C-d> ``, `` Z<C-d> ``, `` z<C-d> ``, `` Z<space> ``, `` z<space> `` |
|
||||
| `select_all` | Select whole document | normal: `` % ``, select: `` % `` |
|
||||
| `select_regex` | Select all regex matches inside selections | normal: `` s ``, select: `` s `` |
|
||||
| `split_selection` | Split selections on regex matches | normal: `` S ``, select: `` S `` |
|
||||
| `split_selection_on_newline` | Split selection on newlines | normal: `` <A-s> ``, select: `` <A-s> `` |
|
||||
| `merge_selections` | Merge selections | normal: `` <A-minus> ``, select: `` <A-minus> `` |
|
||||
| `merge_consecutive_selections` | Merge consecutive selections | normal: `` <A-_> ``, select: `` <A-_> `` |
|
||||
| `search` | Search for regex pattern | normal: `` / ``, `` Z/ ``, `` z/ ``, select: `` / ``, `` Z/ ``, `` z/ `` |
|
||||
| `rsearch` | Reverse search for regex pattern | normal: `` ? ``, `` Z? ``, `` z? ``, select: `` ? ``, `` Z? ``, `` z? `` |
|
||||
| `search_next` | Select next search match | normal: `` n ``, `` Zn ``, `` zn ``, select: `` Zn ``, `` zn `` |
|
||||
| `search_prev` | Select previous search match | normal: `` N ``, `` ZN ``, `` zN ``, select: `` ZN ``, `` zN `` |
|
||||
| `extend_search_next` | Add next search match to selection | select: `` n `` |
|
||||
| `extend_search_prev` | Add previous search match to selection | select: `` N `` |
|
||||
| `search_selection` | Use current selection as search pattern | normal: `` <A-*> ``, select: `` <A-*> `` |
|
||||
| `search_selection_detect_word_boundaries` | Use current selection as the search pattern, automatically wrapping with `\b` on word boundaries | normal: `` * ``, select: `` * `` |
|
||||
| `make_search_word_bounded` | Modify current search to make it word bounded | |
|
||||
| `global_search` | Global search in workspace folder | normal: `` <space>/ ``, select: `` <space>/ `` |
|
||||
| `extend_line` | Select current line, if already selected, extend to another line based on the anchor | |
|
||||
| `extend_line_below` | Select current line, if already selected, extend to next line | normal: `` x ``, select: `` x `` |
|
||||
| `extend_line_above` | Select current line, if already selected, extend to previous line | |
|
||||
| `select_line_above` | Select current line, if already selected, extend or shrink line above based on the anchor | |
|
||||
| `select_line_below` | Select current line, if already selected, extend or shrink line below based on the anchor | |
|
||||
| `extend_to_line_bounds` | Extend selection to line bounds | normal: `` X ``, select: `` X `` |
|
||||
| `shrink_to_line_bounds` | Shrink selection to line bounds | normal: `` <A-x> ``, select: `` <A-x> `` |
|
||||
| `delete_selection` | Delete selection | normal: `` d ``, select: `` d `` |
|
||||
| `delete_selection_noyank` | Delete selection without yanking | normal: `` <A-d> ``, select: `` <A-d> `` |
|
||||
| `change_selection` | Change selection | normal: `` c ``, select: `` c `` |
|
||||
| `change_selection_noyank` | Change selection without yanking | normal: `` <A-c> ``, select: `` <A-c> `` |
|
||||
| `collapse_selection` | Collapse selection into single cursor | normal: `` ; ``, select: `` ; `` |
|
||||
| `flip_selections` | Flip selection cursor and anchor | normal: `` <A-;> ``, select: `` <A-;> `` |
|
||||
| `ensure_selections_forward` | Ensure all selections face forward | normal: `` <A-:> ``, select: `` <A-:> `` |
|
||||
| `insert_mode` | Insert before selection | normal: `` i ``, select: `` i `` |
|
||||
| `append_mode` | Append after selection | normal: `` a ``, select: `` a `` |
|
||||
| `command_mode` | Enter command mode | normal: `` : ``, select: `` : `` |
|
||||
| `file_picker` | Open file picker | normal: `` <space>f ``, select: `` <space>f `` |
|
||||
| `file_picker_in_current_buffer_directory` | Open file picker at current buffer's directory | |
|
||||
| `file_picker_in_current_directory` | Open file picker at current working directory | normal: `` <space>F ``, select: `` <space>F `` |
|
||||
| `file_explorer` | Open file explorer in workspace root | normal: `` <space>e ``, select: `` <space>e `` |
|
||||
| `file_explorer_in_current_buffer_directory` | Open file explorer at current buffer's directory | normal: `` <space>E ``, select: `` <space>E `` |
|
||||
| `file_explorer_in_current_directory` | Open file explorer at current working directory | |
|
||||
| `code_action` | Perform code action | normal: `` <space>a ``, select: `` <space>a `` |
|
||||
| `buffer_picker` | Open buffer picker | normal: `` <space>b ``, select: `` <space>b `` |
|
||||
| `jumplist_picker` | Open jumplist picker | normal: `` <space>j ``, select: `` <space>j `` |
|
||||
| `symbol_picker` | Open symbol picker | |
|
||||
| `syntax_symbol_picker` | Open symbol picker from syntax information | |
|
||||
| `lsp_or_syntax_symbol_picker` | Open symbol picker from LSP or syntax information | normal: `` <space>s ``, select: `` <space>s `` |
|
||||
| `changed_file_picker` | Open changed file picker | normal: `` <space>g ``, select: `` <space>g `` |
|
||||
| `select_references_to_symbol_under_cursor` | Select symbol references | normal: `` <space>h ``, select: `` <space>h `` |
|
||||
| `workspace_symbol_picker` | Open workspace symbol picker | |
|
||||
| `syntax_workspace_symbol_picker` | Open workspace symbol picker from syntax information | |
|
||||
| `lsp_or_syntax_workspace_symbol_picker` | Open workspace symbol picker from LSP or syntax information | normal: `` <space>S ``, select: `` <space>S `` |
|
||||
| `diagnostics_picker` | Open diagnostic picker | normal: `` <space>d ``, select: `` <space>d `` |
|
||||
| `workspace_diagnostics_picker` | Open workspace diagnostic picker | normal: `` <space>D ``, select: `` <space>D `` |
|
||||
| `last_picker` | Open last picker | normal: `` <space>' ``, select: `` <space>' `` |
|
||||
| `insert_at_line_start` | Insert at start of line | normal: `` I ``, select: `` I `` |
|
||||
| `insert_at_line_end` | Insert at end of line | normal: `` A ``, select: `` A `` |
|
||||
| `open_below` | Open new line below selection | normal: `` o ``, select: `` o `` |
|
||||
| `open_above` | Open new line above selection | normal: `` O ``, select: `` O `` |
|
||||
| `normal_mode` | Enter normal mode | normal: `` <esc> ``, select: `` v ``, insert: `` <esc> `` |
|
||||
| `select_mode` | Enter selection extend mode | normal: `` v `` |
|
||||
| `exit_select_mode` | Exit selection mode | select: `` <esc> `` |
|
||||
| `goto_definition` | Goto definition | normal: `` gd ``, select: `` gd `` |
|
||||
| `goto_declaration` | Goto declaration | normal: `` gD ``, select: `` gD `` |
|
||||
| `add_newline_above` | Add newline above | normal: `` [<space> ``, select: `` [<space> `` |
|
||||
| `add_newline_below` | Add newline below | normal: `` ]<space> ``, select: `` ]<space> `` |
|
||||
| `goto_type_definition` | Goto type definition | normal: `` gy ``, select: `` gy `` |
|
||||
| `goto_implementation` | Goto implementation | normal: `` gi ``, select: `` gi `` |
|
||||
| `goto_file_start` | Goto line number <n> else file start | normal: `` gg `` |
|
||||
| `goto_file_end` | Goto file end | |
|
||||
| `extend_to_file_start` | Extend to line number<n> else file start | select: `` gg `` |
|
||||
| `extend_to_file_end` | Extend to file end | |
|
||||
| `goto_file` | Goto files/URLs in selections | normal: `` gf ``, select: `` gf `` |
|
||||
| `goto_file_hsplit` | Goto files in selections (hsplit) | normal: `` <C-w>f ``, `` <space>wf ``, select: `` <C-w>f ``, `` <space>wf `` |
|
||||
| `goto_file_vsplit` | Goto files in selections (vsplit) | normal: `` <C-w>F ``, `` <space>wF ``, select: `` <C-w>F ``, `` <space>wF `` |
|
||||
| `goto_reference` | Goto references | normal: `` gr ``, select: `` gr `` |
|
||||
| `goto_window_top` | Goto window top | normal: `` gt ``, select: `` gt `` |
|
||||
| `goto_window_center` | Goto window center | normal: `` gc ``, select: `` gc `` |
|
||||
| `goto_window_bottom` | Goto window bottom | normal: `` gb ``, select: `` gb `` |
|
||||
| `goto_last_accessed_file` | Goto last accessed file | normal: `` ga ``, select: `` ga `` |
|
||||
| `goto_last_modified_file` | Goto last modified file | normal: `` gm ``, select: `` gm `` |
|
||||
| `goto_last_modification` | Goto last modification | normal: `` g. ``, select: `` g. `` |
|
||||
| `goto_line` | Goto line | normal: `` G ``, select: `` G `` |
|
||||
| `goto_last_line` | Goto last line | normal: `` ge `` |
|
||||
| `extend_to_last_line` | Extend to last line | select: `` ge `` |
|
||||
| `goto_first_diag` | Goto first diagnostic | normal: `` [D ``, select: `` [D `` |
|
||||
| `goto_last_diag` | Goto last diagnostic | normal: `` ]D ``, select: `` ]D `` |
|
||||
| `goto_next_diag` | Goto next diagnostic | normal: `` ]d ``, select: `` ]d `` |
|
||||
| `goto_prev_diag` | Goto previous diagnostic | normal: `` [d ``, select: `` [d `` |
|
||||
| `goto_next_change` | Goto next change | normal: `` ]g ``, select: `` ]g `` |
|
||||
| `goto_prev_change` | Goto previous change | normal: `` [g ``, select: `` [g `` |
|
||||
| `goto_first_change` | Goto first change | normal: `` [G ``, select: `` [G `` |
|
||||
| `goto_last_change` | Goto last change | normal: `` ]G ``, select: `` ]G `` |
|
||||
| `goto_line_start` | Goto line start | normal: `` gh ``, `` <home> ``, select: `` gh ``, insert: `` <home> `` |
|
||||
| `goto_line_end` | Goto line end | normal: `` gl ``, `` <end> ``, select: `` gl `` |
|
||||
| `goto_column` | Goto column | normal: `` g\| `` |
|
||||
| `extend_to_column` | Extend to column | select: `` g\| `` |
|
||||
| `goto_next_buffer` | Goto next buffer | normal: `` gn ``, select: `` gn `` |
|
||||
| `goto_previous_buffer` | Goto previous buffer | normal: `` gp ``, select: `` gp `` |
|
||||
| `goto_line_end_newline` | Goto newline at line end | insert: `` <end> `` |
|
||||
| `goto_first_nonwhitespace` | Goto first non-blank in line | normal: `` gs ``, select: `` gs `` |
|
||||
| `trim_selections` | Trim whitespace from selections | normal: `` _ ``, select: `` _ `` |
|
||||
| `extend_to_line_start` | Extend to line start | select: `` <home> `` |
|
||||
| `extend_to_first_nonwhitespace` | Extend to first non-blank in line | |
|
||||
| `extend_to_line_end` | Extend to line end | select: `` <end> `` |
|
||||
| `extend_to_line_end_newline` | Extend to line end | |
|
||||
| `signature_help` | Show signature help | |
|
||||
| `smart_tab` | Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command. | insert: `` <tab> `` |
|
||||
| `insert_tab` | Insert tab char | insert: `` <S-tab> `` |
|
||||
| `insert_newline` | Insert newline char | insert: `` <C-j> ``, `` <ret> `` |
|
||||
| `insert_char_interactive` | Insert an interactively-chosen char | |
|
||||
| `append_char_interactive` | Append an interactively-chosen char | |
|
||||
| `delete_char_backward` | Delete previous char | insert: `` <C-h> ``, `` <backspace> ``, `` <S-backspace> `` |
|
||||
| `delete_char_forward` | Delete next char | insert: `` <C-d> ``, `` <del> `` |
|
||||
| `delete_word_backward` | Delete previous word | insert: `` <C-w> ``, `` <A-backspace> `` |
|
||||
| `delete_word_forward` | Delete next word | insert: `` <A-d> ``, `` <A-del> `` |
|
||||
| `kill_to_line_start` | Delete till start of line | insert: `` <C-u> `` |
|
||||
| `kill_to_line_end` | Delete till end of line | insert: `` <C-k> `` |
|
||||
| `undo` | Undo change | normal: `` u ``, select: `` u `` |
|
||||
| `redo` | Redo change | normal: `` U ``, select: `` U `` |
|
||||
| `earlier` | Move backward in history | normal: `` <A-u> ``, select: `` <A-u> `` |
|
||||
| `later` | Move forward in history | normal: `` <A-U> ``, select: `` <A-U> `` |
|
||||
| `commit_undo_checkpoint` | Commit changes to new checkpoint | insert: `` <C-s> `` |
|
||||
| `yank` | Yank selection | normal: `` y ``, select: `` y `` |
|
||||
| `yank_to_clipboard` | Yank selections to clipboard | normal: `` <space>y ``, select: `` <space>y `` |
|
||||
| `yank_to_primary_clipboard` | Yank selections to primary clipboard | |
|
||||
| `yank_joined` | Join and yank selections | |
|
||||
| `yank_joined_to_clipboard` | Join and yank selections to clipboard | |
|
||||
| `yank_main_selection_to_clipboard` | Yank main selection to clipboard | normal: `` <space>Y ``, select: `` <space>Y `` |
|
||||
| `yank_joined_to_primary_clipboard` | Join and yank selections to primary clipboard | |
|
||||
| `yank_main_selection_to_primary_clipboard` | Yank main selection to primary clipboard | |
|
||||
| `replace_with_yanked` | Replace with yanked text | normal: `` R ``, select: `` R `` |
|
||||
| `replace_selections_with_clipboard` | Replace selections by clipboard content | normal: `` <space>R ``, select: `` <space>R `` |
|
||||
| `replace_selections_with_primary_clipboard` | Replace selections by primary clipboard | |
|
||||
| `paste_after` | Paste after selection | normal: `` p ``, select: `` p `` |
|
||||
| `paste_before` | Paste before selection | normal: `` P ``, select: `` P `` |
|
||||
| `paste_clipboard_after` | Paste clipboard after selections | normal: `` <space>p ``, select: `` <space>p `` |
|
||||
| `paste_clipboard_before` | Paste clipboard before selections | normal: `` <space>P ``, select: `` <space>P `` |
|
||||
| `paste_primary_clipboard_after` | Paste primary clipboard after selections | |
|
||||
| `paste_primary_clipboard_before` | Paste primary clipboard before selections | |
|
||||
| `indent` | Indent selection | normal: `` <gt> ``, select: `` <gt> `` |
|
||||
| `unindent` | Unindent selection | normal: `` <lt> ``, select: `` <lt> `` |
|
||||
| `format_selections` | Format selection | normal: `` = ``, select: `` = `` |
|
||||
| `join_selections` | Join lines inside selection | normal: `` J ``, select: `` J `` |
|
||||
| `join_selections_space` | Join lines inside selection and select spaces | normal: `` <A-J> ``, select: `` <A-J> `` |
|
||||
| `keep_selections` | Keep selections matching regex | normal: `` K ``, select: `` K `` |
|
||||
| `remove_selections` | Remove selections matching regex | normal: `` <A-K> ``, select: `` <A-K> `` |
|
||||
| `align_selections` | Align selections in column | normal: `` & ``, select: `` & `` |
|
||||
| `keep_primary_selection` | Keep primary selection | normal: `` , ``, select: `` , `` |
|
||||
| `remove_primary_selection` | Remove primary selection | normal: `` <A-,> ``, select: `` <A-,> `` |
|
||||
| `completion` | Invoke completion popup | insert: `` <C-x> `` |
|
||||
| `hover` | Show docs for item under cursor | normal: `` <space>k ``, select: `` <space>k `` |
|
||||
| `toggle_comments` | Comment/uncomment selections | normal: `` <C-c> ``, `` <space>c ``, select: `` <C-c> ``, `` <space>c `` |
|
||||
| `toggle_line_comments` | Line comment/uncomment selections | normal: `` <space><A-c> ``, select: `` <space><A-c> `` |
|
||||
| `toggle_block_comments` | Block comment/uncomment selections | normal: `` <space>C ``, select: `` <space>C `` |
|
||||
| `rotate_selections_forward` | Rotate selections forward | normal: `` ) ``, select: `` ) `` |
|
||||
| `rotate_selections_backward` | Rotate selections backward | normal: `` ( ``, select: `` ( `` |
|
||||
| `rotate_selection_contents_forward` | Rotate selection contents forward | normal: `` <A-)> ``, select: `` <A-)> `` |
|
||||
| `rotate_selection_contents_backward` | Rotate selections contents backward | normal: `` <A-(> ``, select: `` <A-(> `` |
|
||||
| `reverse_selection_contents` | Reverse selections contents | |
|
||||
| `expand_selection` | Expand selection to parent syntax node | normal: `` <A-o> ``, `` <A-up> ``, select: `` <A-o> ``, `` <A-up> `` |
|
||||
| `shrink_selection` | Shrink selection to previously expanded syntax node | normal: `` <A-i> ``, `` <A-down> ``, select: `` <A-i> ``, `` <A-down> `` |
|
||||
| `select_next_sibling` | Select next sibling in the syntax tree | normal: `` <A-n> ``, `` <A-right> ``, select: `` <A-n> ``, `` <A-right> `` |
|
||||
| `select_prev_sibling` | Select previous sibling the in syntax tree | normal: `` <A-p> ``, `` <A-left> ``, select: `` <A-p> ``, `` <A-left> `` |
|
||||
| `select_all_siblings` | Select all siblings of the current node | normal: `` <A-a> ``, select: `` <A-a> `` |
|
||||
| `select_all_children` | Select all children of the current node | normal: `` <A-I> ``, `` <S-A-down> ``, select: `` <A-I> ``, `` <S-A-down> `` |
|
||||
| `jump_forward` | Jump forward on jumplist | normal: `` <C-i> ``, `` <tab> ``, select: `` <C-i> ``, `` <tab> `` |
|
||||
| `jump_backward` | Jump backward on jumplist | normal: `` <C-o> ``, select: `` <C-o> `` |
|
||||
| `save_selection` | Save current selection to jumplist | normal: `` <C-s> ``, select: `` <C-s> `` |
|
||||
| `jump_view_right` | Jump to right split | normal: `` <C-w>l ``, `` <space>wl ``, `` <C-w><C-l> ``, `` <C-w><right> ``, `` <space>w<C-l> ``, `` <space>w<right> ``, select: `` <C-w>l ``, `` <space>wl ``, `` <C-w><C-l> ``, `` <C-w><right> ``, `` <space>w<C-l> ``, `` <space>w<right> `` |
|
||||
| `jump_view_left` | Jump to left split | normal: `` <C-w>h ``, `` <space>wh ``, `` <C-w><C-h> ``, `` <C-w><left> ``, `` <space>w<C-h> ``, `` <space>w<left> ``, select: `` <C-w>h ``, `` <space>wh ``, `` <C-w><C-h> ``, `` <C-w><left> ``, `` <space>w<C-h> ``, `` <space>w<left> `` |
|
||||
| `jump_view_up` | Jump to split above | normal: `` <C-w>k ``, `` <C-w><up> ``, `` <space>wk ``, `` <C-w><C-k> ``, `` <space>w<up> ``, `` <space>w<C-k> ``, select: `` <C-w>k ``, `` <C-w><up> ``, `` <space>wk ``, `` <C-w><C-k> ``, `` <space>w<up> ``, `` <space>w<C-k> `` |
|
||||
| `jump_view_down` | Jump to split below | normal: `` <C-w>j ``, `` <space>wj ``, `` <C-w><C-j> ``, `` <C-w><down> ``, `` <space>w<C-j> ``, `` <space>w<down> ``, select: `` <C-w>j ``, `` <space>wj ``, `` <C-w><C-j> ``, `` <C-w><down> ``, `` <space>w<C-j> ``, `` <space>w<down> `` |
|
||||
| `swap_view_right` | Swap with right split | normal: `` <C-w>L ``, `` <space>wL ``, select: `` <C-w>L ``, `` <space>wL `` |
|
||||
| `swap_view_left` | Swap with left split | normal: `` <C-w>H ``, `` <space>wH ``, select: `` <C-w>H ``, `` <space>wH `` |
|
||||
| `swap_view_up` | Swap with split above | normal: `` <C-w>K ``, `` <space>wK ``, select: `` <C-w>K ``, `` <space>wK `` |
|
||||
| `swap_view_down` | Swap with split below | normal: `` <C-w>J ``, `` <space>wJ ``, select: `` <C-w>J ``, `` <space>wJ `` |
|
||||
| `transpose_view` | Transpose splits | normal: `` <C-w>t ``, `` <space>wt ``, `` <C-w><C-t> ``, `` <space>w<C-t> ``, select: `` <C-w>t ``, `` <space>wt ``, `` <C-w><C-t> ``, `` <space>w<C-t> `` |
|
||||
| `rotate_view` | Goto next window | normal: `` <C-w>w ``, `` <space>ww ``, `` <C-w><C-w> ``, `` <space>w<C-w> ``, select: `` <C-w>w ``, `` <space>ww ``, `` <C-w><C-w> ``, `` <space>w<C-w> `` |
|
||||
| `rotate_view_reverse` | Goto previous window | |
|
||||
| `hsplit` | Horizontal bottom split | normal: `` <C-w>s ``, `` <space>ws ``, `` <C-w><C-s> ``, `` <space>w<C-s> ``, select: `` <C-w>s ``, `` <space>ws ``, `` <C-w><C-s> ``, `` <space>w<C-s> `` |
|
||||
| `hsplit_new` | Horizontal bottom split scratch buffer | normal: `` <C-w>ns ``, `` <space>wns ``, `` <C-w>n<C-s> ``, `` <space>wn<C-s> ``, select: `` <C-w>ns ``, `` <space>wns ``, `` <C-w>n<C-s> ``, `` <space>wn<C-s> `` |
|
||||
| `vsplit` | Vertical right split | normal: `` <C-w>v ``, `` <space>wv ``, `` <C-w><C-v> ``, `` <space>w<C-v> ``, select: `` <C-w>v ``, `` <space>wv ``, `` <C-w><C-v> ``, `` <space>w<C-v> `` |
|
||||
| `vsplit_new` | Vertical right split scratch buffer | normal: `` <C-w>nv ``, `` <space>wnv ``, `` <C-w>n<C-v> ``, `` <space>wn<C-v> ``, select: `` <C-w>nv ``, `` <space>wnv ``, `` <C-w>n<C-v> ``, `` <space>wn<C-v> `` |
|
||||
| `wclose` | Close window | normal: `` <C-w>q ``, `` <space>wq ``, `` <C-w><C-q> ``, `` <space>w<C-q> ``, select: `` <C-w>q ``, `` <space>wq ``, `` <C-w><C-q> ``, `` <space>w<C-q> `` |
|
||||
| `wonly` | Close windows except current | normal: `` <C-w>o ``, `` <space>wo ``, `` <C-w><C-o> ``, `` <space>w<C-o> ``, select: `` <C-w>o ``, `` <space>wo ``, `` <C-w><C-o> ``, `` <space>w<C-o> `` |
|
||||
| `select_register` | Select register | normal: `` " ``, select: `` " `` |
|
||||
| `insert_register` | Insert register | insert: `` <C-r> `` |
|
||||
| `copy_between_registers` | Copy between two registers | |
|
||||
| `align_view_middle` | Align view middle | normal: `` Zm ``, `` zm ``, select: `` Zm ``, `` zm `` |
|
||||
| `align_view_top` | Align view top | normal: `` Zt ``, `` zt ``, select: `` Zt ``, `` zt `` |
|
||||
| `align_view_center` | Align view center | normal: `` Zc ``, `` Zz ``, `` zc ``, `` zz ``, select: `` Zc ``, `` Zz ``, `` zc ``, `` zz `` |
|
||||
| `align_view_bottom` | Align view bottom | normal: `` Zb ``, `` zb ``, select: `` Zb ``, `` zb `` |
|
||||
| `scroll_up` | Scroll view up | normal: `` Zk ``, `` zk ``, `` Z<up> ``, `` z<up> ``, select: `` Zk ``, `` zk ``, `` Z<up> ``, `` z<up> `` |
|
||||
| `scroll_down` | Scroll view down | normal: `` Zj ``, `` zj ``, `` Z<down> ``, `` z<down> ``, select: `` Zj ``, `` zj ``, `` Z<down> ``, `` z<down> `` |
|
||||
| `match_brackets` | Goto matching bracket | normal: `` mm ``, select: `` mm `` |
|
||||
| `surround_add` | Surround add | normal: `` ms ``, select: `` ms `` |
|
||||
| `surround_replace` | Surround replace | normal: `` mr ``, select: `` mr `` |
|
||||
| `surround_delete` | Surround delete | normal: `` md ``, select: `` md `` |
|
||||
| `select_textobject_around` | Select around object | normal: `` ma ``, select: `` ma `` |
|
||||
| `select_textobject_inner` | Select inside object | normal: `` mi ``, select: `` mi `` |
|
||||
| `goto_next_function` | Goto next function | normal: `` ]f ``, select: `` ]f `` |
|
||||
| `goto_prev_function` | Goto previous function | normal: `` [f ``, select: `` [f `` |
|
||||
| `goto_next_class` | Goto next type definition | normal: `` ]t ``, select: `` ]t `` |
|
||||
| `goto_prev_class` | Goto previous type definition | normal: `` [t ``, select: `` [t `` |
|
||||
| `goto_next_parameter` | Goto next parameter | normal: `` ]a ``, select: `` ]a `` |
|
||||
| `goto_prev_parameter` | Goto previous parameter | normal: `` [a ``, select: `` [a `` |
|
||||
| `goto_next_comment` | Goto next comment | normal: `` ]c ``, select: `` ]c `` |
|
||||
| `goto_prev_comment` | Goto previous comment | normal: `` [c ``, select: `` [c `` |
|
||||
| `goto_next_test` | Goto next test | normal: `` ]T ``, select: `` ]T `` |
|
||||
| `goto_prev_test` | Goto previous test | normal: `` [T ``, select: `` [T `` |
|
||||
| `goto_next_xml_element` | Goto next (X)HTML element | normal: `` ]x ``, select: `` ]x `` |
|
||||
| `goto_prev_xml_element` | Goto previous (X)HTML element | normal: `` [x ``, select: `` [x `` |
|
||||
| `goto_next_entry` | Goto next pairing | normal: `` ]e ``, select: `` ]e `` |
|
||||
| `goto_prev_entry` | Goto previous pairing | normal: `` [e ``, select: `` [e `` |
|
||||
| `goto_next_paragraph` | Goto next paragraph | normal: `` ]p ``, select: `` ]p `` |
|
||||
| `goto_prev_paragraph` | Goto previous paragraph | normal: `` [p ``, select: `` [p `` |
|
||||
| `dap_launch` | Launch debug target | normal: `` <space>Gl ``, select: `` <space>Gl `` |
|
||||
| `dap_restart` | Restart debugging session | normal: `` <space>Gr ``, select: `` <space>Gr `` |
|
||||
| `dap_toggle_breakpoint` | Toggle breakpoint | normal: `` <space>Gb ``, select: `` <space>Gb `` |
|
||||
| `dap_continue` | Continue program execution | normal: `` <space>Gc ``, select: `` <space>Gc `` |
|
||||
| `dap_pause` | Pause program execution | normal: `` <space>Gh ``, select: `` <space>Gh `` |
|
||||
| `dap_step_in` | Step in | normal: `` <space>Gi ``, select: `` <space>Gi `` |
|
||||
| `dap_step_out` | Step out | normal: `` <space>Go ``, select: `` <space>Go `` |
|
||||
| `dap_next` | Step to next | normal: `` <space>Gn ``, select: `` <space>Gn `` |
|
||||
| `dap_variables` | List variables | normal: `` <space>Gv ``, select: `` <space>Gv `` |
|
||||
| `dap_terminate` | End debug session | normal: `` <space>Gt ``, select: `` <space>Gt `` |
|
||||
| `dap_edit_condition` | Edit breakpoint condition on current line | normal: `` <space>G<C-c> ``, select: `` <space>G<C-c> `` |
|
||||
| `dap_edit_log` | Edit breakpoint log message on current line | normal: `` <space>G<C-l> ``, select: `` <space>G<C-l> `` |
|
||||
| `dap_switch_thread` | Switch current thread | normal: `` <space>Gst ``, select: `` <space>Gst `` |
|
||||
| `dap_switch_stack_frame` | Switch stack frame | normal: `` <space>Gsf ``, select: `` <space>Gsf `` |
|
||||
| `dap_enable_exceptions` | Enable exception breakpoints | normal: `` <space>Ge ``, select: `` <space>Ge `` |
|
||||
| `dap_disable_exceptions` | Disable exception breakpoints | normal: `` <space>GE ``, select: `` <space>GE `` |
|
||||
| `shell_pipe` | Pipe selections through shell command | normal: `` \| ``, select: `` \| `` |
|
||||
| `shell_pipe_to` | Pipe selections into shell command ignoring output | normal: `` <A-\|> ``, select: `` <A-\|> `` |
|
||||
| `shell_insert_output` | Insert shell command output before selections | normal: `` ! ``, select: `` ! `` |
|
||||
| `shell_append_output` | Append shell command output after selections | normal: `` <A-!> ``, select: `` <A-!> `` |
|
||||
| `shell_keep_pipe` | Filter selections with shell predicate | normal: `` $ ``, select: `` $ `` |
|
||||
| `suspend` | Suspend and return to shell | normal: `` <C-z> ``, select: `` <C-z> `` |
|
||||
| `rename_symbol` | Rename symbol | normal: `` <space>r ``, select: `` <space>r `` |
|
||||
| `increment` | Increment item under cursor | normal: `` <C-a> ``, select: `` <C-a> `` |
|
||||
| `decrement` | Decrement item under cursor | normal: `` <C-x> ``, select: `` <C-x> `` |
|
||||
| `record_macro` | Record macro | normal: `` Q ``, select: `` Q `` |
|
||||
| `replay_macro` | Replay macro | normal: `` q ``, select: `` q `` |
|
||||
| `command_palette` | Open command palette | normal: `` <space>? ``, select: `` <space>? `` |
|
||||
| `goto_word` | Jump to a two-character label | normal: `` gw `` |
|
||||
| `extend_to_word` | Extend to a two-character label | select: `` gw `` |
|
||||
| `goto_next_tabstop` | Goto next snippet placeholder | |
|
||||
| `goto_prev_tabstop` | Goto next snippet placeholder | |
|
||||
| `rotate_selections_first` | Make the first selection your primary one | |
|
||||
| `rotate_selections_last` | Make the last selection your primary one | |
|
@@ -1,38 +1,31 @@
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `:quit`, `:q` | Close the current view. |
|
||||
| `:quit!`, `:q!` | Force close the current view, ignoring unsaved changes. |
|
||||
| `:open`, `:o`, `:edit`, `:e` | Open a file from disk into the current view. |
|
||||
| `:quit!`, `:q!` | Close the current view forcefully (ignoring unsaved changes). |
|
||||
| `:open`, `:o` | Open a file from disk into the current view. |
|
||||
| `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. |
|
||||
| `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully, ignoring unsaved changes. |
|
||||
| `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully (ignoring unsaved changes). |
|
||||
| `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. |
|
||||
| `:buffer-close-others!`, `:bco!`, `:bcloseother!` | Force close all buffers but the currently focused one. |
|
||||
| `:buffer-close-all`, `:bca`, `:bcloseall` | Close all buffers without quitting. |
|
||||
| `:buffer-close-all!`, `:bca!`, `:bcloseall!` | Force close all buffers ignoring unsaved changes without quitting. |
|
||||
| `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
|
||||
| `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
|
||||
| `:buffer-close-others!`, `:bco!`, `:bcloseother!` | Close all buffers but the currently focused one. |
|
||||
| `:buffer-close-all`, `:bca`, `:bcloseall` | Close all buffers, without quiting. |
|
||||
| `:buffer-close-all!`, `:bca!`, `:bcloseall!` | Close all buffers forcefully (ignoring unsaved changes), without quiting. |
|
||||
| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
|
||||
| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt) |
|
||||
| `:write-buffer-close`, `:wbc` | Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt) |
|
||||
| `: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) |
|
||||
| `:new`, `:n` | Create a new scratch buffer. |
|
||||
| `:format`, `:fmt` | Format the file using an external formatter or language server. |
|
||||
| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
|
||||
| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
|
||||
| `:format`, `:fmt` | Format the file using the LSP formatter. |
|
||||
| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
|
||||
| `:line-ending` | Set the document's default line ending. Options: crlf, lf, cr, ff, nel. |
|
||||
| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
|
||||
| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
|
||||
| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
|
||||
| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
|
||||
| `:write-all`, `:wa` | Write changes from all buffers to disk. |
|
||||
| `:write-all!`, `:wa!` | Forcefully write changes from all buffers to disk creating necessary subdirectories. |
|
||||
| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
|
||||
| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
|
||||
| `:write-all`, `:wa` | Write changes from all views to disk. |
|
||||
| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all views to disk and close all views. |
|
||||
| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all views to disk and close all views forcefully (ignoring unsaved changes). |
|
||||
| `:quit-all`, `:qa` | Close all views. |
|
||||
| `:quit-all!`, `:qa!` | Force close all views ignoring unsaved changes. |
|
||||
| `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). |
|
||||
| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
|
||||
| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
|
||||
| `:theme` | Change the editor theme (show current theme if no name specified). |
|
||||
| `:yank-join` | Yank joined selections. A separator can be provided as first argument. Default value is newline. |
|
||||
| `:cquit!`, `:cq!` | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). |
|
||||
| `:theme` | Change the editor theme. |
|
||||
| `:clipboard-yank` | Yank main selection into system clipboard. |
|
||||
| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
|
||||
| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
|
||||
@@ -46,16 +39,9 @@
|
||||
| `:show-clipboard-provider` | Show clipboard provider name in status bar. |
|
||||
| `:change-current-directory`, `:cd` | Change the current working directory. |
|
||||
| `:show-directory`, `:pwd` | Show the current working directory. |
|
||||
| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
|
||||
| `:character-info`, `:char` | Get info about the character under the primary cursor. |
|
||||
| `:reload`, `:rl` | Discard changes and reload from the source file. |
|
||||
| `:reload-all`, `:rla` | Discard changes and reload all documents from the source files. |
|
||||
| `:update`, `:u` | Write changes only if the file has been modified. |
|
||||
| `:lsp-workspace-command` | Open workspace command picker |
|
||||
| `:lsp-restart` | Restarts the given language servers, or all language servers that are used by the current file if no arguments are supplied |
|
||||
| `:lsp-stop` | Stops the given language servers, or all language servers that are used by the current file if no arguments are supplied |
|
||||
| `:encoding` | Set encoding based on `https://encoding.spec.whatwg.org` |
|
||||
| `:reload` | Discard changes and reload from the source file. |
|
||||
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
|
||||
| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
|
||||
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
|
||||
| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
|
||||
| `:debug-eval` | Evaluate expression in current debug context. |
|
||||
@@ -65,27 +51,7 @@
|
||||
| `:hsplit-new`, `:hnew` | Open a scratch buffer in a horizontal split. |
|
||||
| `:tutor` | Open the tutorial. |
|
||||
| `:goto`, `:g` | Go to line number. |
|
||||
| `:set-language`, `:lang` | Set the language of current buffer (show current language if no value specified). |
|
||||
| `:set-option`, `:set` | Set a config option at runtime.<br>For example to disable smart case search, use `:set search.smart-case false`. |
|
||||
| `:toggle-option`, `:toggle` | Toggle a config option at runtime.<br>For example to toggle smart case search, use `:toggle search.smart-case`. |
|
||||
| `:get-option`, `:get` | Get the current value of a config option. |
|
||||
| `:set-option`, `:set` | Set a config option at runtime |
|
||||
| `:sort` | Sort ranges in selection. |
|
||||
| `:reflow` | Hard-wrap the current selection of lines to a given width. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries. |
|
||||
| `:config-reload` | Refresh user config. |
|
||||
| `:config-open` | Open the user config.toml file. |
|
||||
| `:config-open-workspace` | Open the workspace config.toml file. |
|
||||
| `:log-open` | Open the helix log file. |
|
||||
| `:insert-output` | Run shell command, inserting output before each selection. |
|
||||
| `:append-output` | Run shell command, appending output after each selection. |
|
||||
| `:pipe`, `:\|` | Pipe each selection to the shell command. |
|
||||
| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
|
||||
| `:run-shell-command`, `:sh`, `:!` | Run a shell command |
|
||||
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
|
||||
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
|
||||
| `:redraw` | Clear and re-render the whole UI |
|
||||
| `:move`, `:mv` | Move the current buffer and its corresponding file to a different path |
|
||||
| `:yank-diagnostic` | Yank diagnostic(s) under primary cursor to register, or clipboard by default |
|
||||
| `:read`, `:r` | Load a file into buffer |
|
||||
| `:echo` | Prints the given arguments to the statusline. |
|
||||
| `:noop` | Does nothing. |
|
||||
| `:rsort` | Sort ranges in selection in reverse order. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Guides
|
||||
|
||||
This section contains guides for adding new language server configurations,
|
||||
tree-sitter grammars, textobject and rainbow bracket queries, and other similar items.
|
||||
tree-sitter grammers, textobject queries, etc.
|
||||
|
@@ -1,49 +1,90 @@
|
||||
## Adding new languages to Helix
|
||||
|
||||
In order to add a new language to Helix, you will need to follow the steps
|
||||
below.
|
||||
# Adding languages
|
||||
|
||||
## Language configuration
|
||||
|
||||
1. Add a new `[[language]]` entry in the `languages.toml` file and provide the
|
||||
necessary configuration for the new language. For more information on
|
||||
language configuration, refer to the
|
||||
[language configuration section](../languages.md) of the documentation.
|
||||
A new language server can be added by extending the `[language-server]` table in the same file.
|
||||
2. If you are adding a new language or updating an existing language server
|
||||
configuration, run the command `cargo xtask docgen` to update the
|
||||
[Language Support](../lang-support.md) documentation.
|
||||
To add a new language, you need to add a `language` entry to the
|
||||
[`languages.toml`][languages.toml] found in the root of the repository;
|
||||
this `languages.toml` file is included at compilation time, and is
|
||||
distinct from the `languages.toml` file in the user's [configuration
|
||||
directory](../configuration.md).
|
||||
|
||||
> 💡 If you are adding a new Language Server configuration, make sure to update
|
||||
> the
|
||||
> [Language Server Wiki](https://github.com/helix-editor/helix/wiki/Language-Server-Configurations)
|
||||
> with the installation instructions.
|
||||
```toml
|
||||
[[language]]
|
||||
name = "mylang"
|
||||
scope = "scope.mylang"
|
||||
injection-regex = "^mylang$"
|
||||
file-types = ["mylang", "myl"]
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
```
|
||||
|
||||
These are the available keys and descriptions for the file.
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `name` | The name of the language |
|
||||
| `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 |
|
||||
| `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. |
|
||||
| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. Extensions and full file names are supported. |
|
||||
| `shebangs` | The interpreters from the shebang line, for example `["sh", "bash"]` |
|
||||
| `roots` | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
|
||||
| `auto-format` | Whether to autoformat this language when saving |
|
||||
| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
|
||||
| `comment-token` | The token to use as a comment-token |
|
||||
| `indent` | The indent to use. Has sub keys `tab-width` and `unit` |
|
||||
| `config` | Language server configuration |
|
||||
| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
|
||||
|
||||
## Grammar configuration
|
||||
|
||||
1. If a tree-sitter grammar is available for the new language, add a new
|
||||
`[[grammar]]` entry to the `languages.toml` file.
|
||||
2. If you are testing the grammar locally, you can use the `source.path` key
|
||||
with an absolute path to the grammar. However, before submitting a pull
|
||||
request, make sure to switch to using `source.git`.
|
||||
If a tree-sitter grammar is available for the language, add a new `grammar`
|
||||
entry to `languages.toml`.
|
||||
|
||||
```toml
|
||||
[[grammar]]
|
||||
name = "mylang"
|
||||
source = { git = "https://github.com/example/mylang", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }
|
||||
```
|
||||
|
||||
Grammar configuration takes these keys:
|
||||
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `name` | The name of the tree-sitter grammar |
|
||||
| `source` | The method of fetching the grammar - a table with a schema defined below |
|
||||
|
||||
Where `source` is a table with either these keys when using a grammar from a
|
||||
git repository:
|
||||
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `git` | A git remote URL from which the grammar should be cloned |
|
||||
| `rev` | The revision (commit hash or tag) which should be fetched |
|
||||
| `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 |
|
||||
|
||||
Or a `path` key with an absolute path to a locally available grammar directory.
|
||||
|
||||
## Queries
|
||||
|
||||
1. In order to provide syntax highlighting and indentation for the new language,
|
||||
you will need to add queries.
|
||||
2. Create a new directory for the language with the path
|
||||
`runtime/queries/<name>/`.
|
||||
3. Refer to the
|
||||
[tree-sitter website](https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#highlights)
|
||||
for more information on writing queries.
|
||||
4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
|
||||
For a language to have syntax-highlighting and indentation among
|
||||
other things, you have to add queries. Add a directory for your
|
||||
language with the path `runtime/queries/<name>/`. The tree-sitter
|
||||
[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
|
||||
gives more info on how to write queries.
|
||||
|
||||
## Common issues
|
||||
> NOTE: When evaluating queries, the first matching query takes
|
||||
precedence, which is different from other editors like neovim where
|
||||
the last matching query supersedes the ones before it. See
|
||||
[this issue][neovim-query-precedence] for an example.
|
||||
|
||||
- If you encounter errors when running Helix after switching branches, you may
|
||||
need to update the tree-sitter grammars. Run the command `hx --grammar fetch`
|
||||
to fetch the grammars and `hx --grammar build` to build any out-of-date
|
||||
grammars.
|
||||
- If a parser is causing a segfault, or you want to remove it, make sure to
|
||||
remove the compiled parser located at `runtime/grammars/<name>.so`.
|
||||
- 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.
|
||||
## Common Issues
|
||||
|
||||
- If you get errors when running after switching branches, you may have to update the tree-sitter grammars. Run `hx --grammar fetch` to fetch the grammars and `hx --grammar build` to build any out-of-date grammars.
|
||||
|
||||
- If a parser is segfaulting or you want to remove the parser, make sure to remove the compiled parser in `runtime/grammar/<name>.so`
|
||||
|
||||
- The indents query is `indents.toml`, *not* `indents.scm`. See [this](https://github.com/helix-editor/helix/issues/114) issue for more information.
|
||||
|
||||
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
|
||||
[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml
|
||||
[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
|
||||
|
@@ -1,373 +0,0 @@
|
||||
## Adding indent queries
|
||||
|
||||
Helix uses tree-sitter to correctly indent new lines. This requires a tree-
|
||||
sitter grammar and an `indent.scm` query file placed in `runtime/queries/
|
||||
{language}/indents.scm`. The indentation for a line is calculated by traversing
|
||||
the syntax tree from the lowest node at the beginning of the new line (see
|
||||
[Indent queries](#indent-queries)). Each of these nodes contributes to the total
|
||||
indent when it is captured by the query (in what way depends on the name of
|
||||
the capture.
|
||||
|
||||
Note that it matters where these added indents begin. For example,
|
||||
multiple indent level increases that start on the same line only increase
|
||||
the total indent level by 1. See [Capture types](#capture-types).
|
||||
|
||||
By default, Helix uses the `hybrid` indentation heuristic. This means that
|
||||
indent queries are not used to compute the expected absolute indentation of a
|
||||
line but rather the expected difference in indentation between the new and an
|
||||
already existing line. This difference is then added to the actual indentation
|
||||
of the already existing line. Since this makes errors in the indent queries
|
||||
harder to find, it is recommended to disable it when testing via
|
||||
`:set indent-heuristic tree-sitter`. The rest of this guide assumes that
|
||||
the `tree-sitter` heuristic is used.
|
||||
|
||||
## Indent queries
|
||||
|
||||
When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
|
||||
the indent level for the new line, the query in `indents.scm` is run on the
|
||||
document. The starting position of the query is the end of the line above where
|
||||
a new line will be inserted.
|
||||
|
||||
For `o`, the inserted line is the line below the cursor, so that starting
|
||||
position of the query is the end of the current line.
|
||||
|
||||
```rust
|
||||
fn need_hero(some_hero: Hero, life: Life) -> {
|
||||
matches!(some_hero, Hero { // ←─────────────────╮
|
||||
strong: true,//←╮ ↑ ↑ │
|
||||
fast: true, // │ │ ╰── query start │
|
||||
sure: true, // │ ╰───── cursor ├─ traversal
|
||||
soon: true, // ╰──────── new line inserted │ start node
|
||||
}) && // │
|
||||
// ↑ │
|
||||
// ╰───────────────────────────────────────────────╯
|
||||
some_hero > life
|
||||
}
|
||||
```
|
||||
|
||||
For `O`, the newly inserted line is the *current* line, so the starting position
|
||||
of the query is the end of the line above the cursor.
|
||||
|
||||
```rust
|
||||
fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
|
||||
matches!(some_hero, Hero { // ←╮ ↑ │
|
||||
strong: true,// ↑ ╭───╯ │ │
|
||||
fast: true, // │ │ query start ─╯ │
|
||||
sure: true, // ╰───┼ cursor ├─ traversal
|
||||
soon: true, // ╰ new line inserted │ start node
|
||||
}) && // │
|
||||
some_hero > life // │
|
||||
} // ←──────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
From this starting node, the syntax tree is traversed up until the root node.
|
||||
Each indent capture is collected along the way, and then combined according to
|
||||
their [capture types](#capture-types) and [scopes](#scopes) to a final indent
|
||||
level for the line.
|
||||
|
||||
### Capture types
|
||||
|
||||
- `@indent` (default scope `tail`):
|
||||
Increase the indent level by 1. Multiple occurrences in the same line *do not*
|
||||
stack. If there is at least one `@indent` and one `@outdent` capture on the
|
||||
same line, the indent level isn't changed at all.
|
||||
- `@outdent` (default scope `all`):
|
||||
Decrease the indent level by 1. The same rules as for `@indent` apply.
|
||||
- `@indent.always` (default scope `tail`):
|
||||
Increase the indent level by 1. Multiple occurrences on the same line *do*
|
||||
stack. The final indent level is `@indent.always` – `@outdent.always`. If
|
||||
an `@indent` and an `@indent.always` are on the same line, the `@indent` is
|
||||
ignored.
|
||||
- `@outdent.always` (default scope `all`):
|
||||
Decrease the indent level by 1. The same rules as for `@indent.always` apply.
|
||||
- `@align` (default scope `all`):
|
||||
Align everything inside this node to some anchor. The anchor is given
|
||||
by the start of the node captured by `@anchor` in the same pattern.
|
||||
Every pattern with an `@align` should contain exactly one `@anchor`.
|
||||
Indent (and outdent) for nodes below (in terms of their starting line)
|
||||
the `@align` node is added to the indentation required for alignment.
|
||||
- `@extend`:
|
||||
Extend the range of this node to the end of the line and to lines that are
|
||||
indented more than the line that this node starts on. This is useful for
|
||||
languages like Python, where for the purpose of indentation some nodes (like
|
||||
functions or classes) should also contain indented lines that follow them.
|
||||
- `@extend.prevent-once`:
|
||||
Prevents the first extension of an ancestor of this node. For example, in Python
|
||||
a return expression always ends the block that it is in. Note that this only
|
||||
stops the extension of the next `@extend` capture. If multiple ancestors are
|
||||
captured, only the extension of the innermost one is prevented. All other
|
||||
ancestors are unaffected (regardless of whether the innermost ancestor would
|
||||
actually have been extended).
|
||||
|
||||
#### `@indent` / `@outdent`
|
||||
|
||||
Consider this example:
|
||||
|
||||
```rust
|
||||
fn shout(things: Vec<Thing>) {
|
||||
// ↑
|
||||
// ├───────────────────────╮ indent level
|
||||
// @indent ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
|
||||
// │
|
||||
let it_all = |out| { things.filter(|thing| { // │ 1
|
||||
// ↑ ↑ │
|
||||
// ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
|
||||
// @indent @indent │
|
||||
// │ 2
|
||||
thing.can_do_with(out) // │
|
||||
})}; // ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
|
||||
//↑↑↑ │ 1
|
||||
} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
|
||||
// 3x @outdent
|
||||
```
|
||||
|
||||
```scm
|
||||
((block) @indent)
|
||||
["}" ")"] @outdent
|
||||
```
|
||||
|
||||
Note how on the second line, we have two blocks begin on the same line. In this
|
||||
case, since both captures occur on the same line, they are combined and only
|
||||
result in a net increase of 1. Also note that the closing `}`s are part of the
|
||||
`@indent` captures, but the 3 `@outdent`s also combine into 1 and result in that
|
||||
line losing one indent level.
|
||||
|
||||
#### `@extend` / `@extend.prevent-once`
|
||||
|
||||
For an example of where `@extend` can be useful, consider Python, which is
|
||||
whitespace-sensitive.
|
||||
|
||||
```scm
|
||||
]
|
||||
(parenthesized_expression)
|
||||
(function_definition)
|
||||
(class_definition)
|
||||
] @indent
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
class Hero:
|
||||
def __init__(self, strong, fast, sure, soon):# ←─╮
|
||||
self.is_strong = strong # │
|
||||
self.is_fast = fast # ╭─── query start │
|
||||
self.is_sure = sure # │ ╭─ cursor │
|
||||
self.is_soon = soon # │ │ │
|
||||
# ↑ ↑ │ │ │
|
||||
# │ ╰──────╯ │ │
|
||||
# ╰─────────────────────╯ │
|
||||
# ├─ traversal
|
||||
def need_hero(self, life): # │ start node
|
||||
return ( # │
|
||||
self.is_strong # │
|
||||
and self.is_fast # │
|
||||
and self.is_sure # │
|
||||
and self.is_soon # │
|
||||
and self > life # │
|
||||
) # ←─────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Without braces to catch the scope of the function, the smallest descendant of
|
||||
the cursor on a line feed ends up being the entire inside of the class. Because
|
||||
of this, it will miss the entire function node and its indent capture, leading
|
||||
to an indent level one too small.
|
||||
|
||||
To address this case, `@extend` tells helix to "extend" the captured node's span
|
||||
to the line feed and every consecutive line that has a greater indent level than
|
||||
the line of the node.
|
||||
|
||||
```scm
|
||||
(parenthesized_expression) @indent
|
||||
|
||||
]
|
||||
(function_definition)
|
||||
(class_definition)
|
||||
] @indent @extend
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
class Hero:
|
||||
def __init__(self, strong, fast, sure, soon):# ←─╮
|
||||
self.is_strong = strong # │
|
||||
self.is_fast = fast # ╭─── query start ├─ traversal
|
||||
self.is_sure = sure # │ ╭─ cursor │ start node
|
||||
self.is_soon = soon # │ │ ←───────────────╯
|
||||
# ↑ ↑ │ │
|
||||
# │ ╰──────╯ │
|
||||
# ╰─────────────────────╯
|
||||
def need_hero(self, life):
|
||||
return (
|
||||
self.is_strong
|
||||
and self.is_fast
|
||||
and self.is_sure
|
||||
and self.is_soon
|
||||
and self > life
|
||||
)
|
||||
```
|
||||
|
||||
Furthermore, there are some cases where extending to everything with a greater
|
||||
indent level may not be desirable. Consider the `need_hero` function above. If
|
||||
our cursor is on the last line of the returned expression.
|
||||
|
||||
```python
|
||||
class Hero:
|
||||
def __init__(self, strong, fast, sure, soon):
|
||||
self.is_strong = strong
|
||||
self.is_fast = fast
|
||||
self.is_sure = sure
|
||||
self.is_soon = soon
|
||||
|
||||
def need_hero(self, life):
|
||||
return (
|
||||
self.is_strong
|
||||
and self.is_fast
|
||||
and self.is_sure
|
||||
and self.is_soon
|
||||
and self > life
|
||||
) # ←─── cursor
|
||||
#←────────── where cursor should go on new line
|
||||
```
|
||||
|
||||
In Python, the are a few tokens that will always end a scope, such as a return
|
||||
statement. Since the scope ends, so should the indent level. But because the
|
||||
function span is extended to every line with a greater indent level, a new line
|
||||
would just continue on the same level. And an `@outdent` would not help us here
|
||||
either, since it would cause everything in the parentheses to become outdented
|
||||
as well.
|
||||
|
||||
To help, we need to signal an end to the extension. We can do this with
|
||||
`@extend.prevent-once`.
|
||||
|
||||
```scm
|
||||
(parenthesized_expression) @indent
|
||||
|
||||
]
|
||||
(function_definition)
|
||||
(class_definition)
|
||||
] @indent @extend
|
||||
|
||||
(return_statement) @extend.prevent-once
|
||||
```
|
||||
|
||||
#### `@indent.always` / `@outdent.always`
|
||||
|
||||
As mentioned before, normally if there is more than one `@indent` or `@outdent`
|
||||
capture on the same line, they are combined.
|
||||
|
||||
Sometimes, there are cases when you may want to ensure that every indent capture
|
||||
is additive, regardless of how many occur on the same line. Consider this
|
||||
example in YAML.
|
||||
|
||||
```yaml
|
||||
- foo: bar
|
||||
# ↑ ↑
|
||||
# │ ╰─────────────── start of map
|
||||
# ╰───────────────── start of list element
|
||||
baz: quux # ←─── cursor
|
||||
# ←───────────── where the cursor should go on a new line
|
||||
garply: waldo
|
||||
- quux:
|
||||
bar: baz
|
||||
xyzzy: thud
|
||||
fred: plugh
|
||||
```
|
||||
|
||||
In YAML, you often have lists of maps. In these cases, the syntax is such that
|
||||
the list element and the map both start on the same line. But we really do want
|
||||
to start an indentation for each of these so that subsequent keys in the map
|
||||
hang over the list and align properly. This is where `@indent.always` helps.
|
||||
|
||||
```scm
|
||||
((block_sequence_item) @item @indent.always @extend
|
||||
(#not-one-line? @item))
|
||||
|
||||
((block_mapping_pair
|
||||
key: (_) @key
|
||||
value: (_) @val
|
||||
(#not-same-line? @key @val)
|
||||
) @indent.always @extend
|
||||
)
|
||||
```
|
||||
|
||||
## Predicates
|
||||
|
||||
In some cases, an S-expression cannot express exactly what pattern should be matched.
|
||||
For that, tree-sitter allows for predicates to appear anywhere within a pattern,
|
||||
similar to how `#set!` declarations work:
|
||||
|
||||
```scm
|
||||
(some_kind
|
||||
(child_kind) @indent
|
||||
(#predicate? arg1 arg2 ...)
|
||||
)
|
||||
```
|
||||
|
||||
The number of arguments depends on the predicate that's used.
|
||||
Each argument is either a capture (`@name`) or a string (`"some string"`).
|
||||
The following predicates are supported by tree-sitter:
|
||||
|
||||
- `#eq?`/`#not-eq?`:
|
||||
The first argument (a capture) must/must not be equal to the second argument
|
||||
(a capture or a string).
|
||||
|
||||
- `#match?`/`#not-match?`:
|
||||
The first argument (a capture) must/must not match the regex given in the
|
||||
second argument (a string).
|
||||
|
||||
- `#any-of?`/`#not-any-of?`:
|
||||
The first argument (a capture) must/must not be one of the other arguments
|
||||
(strings).
|
||||
|
||||
Additionally, we support some custom predicates for indent queries:
|
||||
|
||||
- `#not-kind-eq?`:
|
||||
The kind of the first argument (a capture) must not be equal to the second
|
||||
argument (a string).
|
||||
|
||||
- `#same-line?`/`#not-same-line?`:
|
||||
The captures given by the 2 arguments must/must not start on the same line.
|
||||
|
||||
- `#one-line?`/`#not-one-line?`:
|
||||
The captures given by the fist argument must/must span a total of one line.
|
||||
|
||||
### Scopes
|
||||
|
||||
Added indents don't always apply to the whole node. For example, in most
|
||||
cases when a node should be indented, we actually only want everything
|
||||
except for its first line to be indented. For this, there are several
|
||||
scopes (more scopes may be added in the future if required):
|
||||
|
||||
- `tail`:
|
||||
This scope applies to everything except for the first line of the
|
||||
captured node.
|
||||
- `all`:
|
||||
This scope applies to the whole captured node. This is only different from
|
||||
`tail` when the captured node is the first node on its line.
|
||||
|
||||
For example, imagine we have the following function
|
||||
|
||||
```rust
|
||||
fn aha() { // ←─────────────────────────────────────╮
|
||||
let take = "on me"; // ←──────────────╮ scope: │
|
||||
let take = "me on"; // ├─ "tail" ├─ (block) @indent
|
||||
let ill = be_gone_days(1 || 2); // │ │
|
||||
} // ←───────────────────────────────────┴──────────┴─ "}" @outdent
|
||||
// scope: "all"
|
||||
```
|
||||
|
||||
We can write the following query with the `#set!` declaration:
|
||||
|
||||
```scm
|
||||
((block) @indent
|
||||
(#set! "scope" "tail"))
|
||||
("}" @outdent
|
||||
(#set! "scope" "all"))
|
||||
```
|
||||
|
||||
As we can see, the "tail" scope covers the node, except for the first line.
|
||||
Everything up to and including the closing brace gets an indent level of 1.
|
||||
Then, on the closing brace, we encounter an outdent with a scope of "all", which
|
||||
means the first line is included, and the indent level is cancelled out on this
|
||||
line. (Note these scopes are the defaults for `@indent` and `@outdent`—they are
|
||||
written explicitly for demonstration.)
|
@@ -1,65 +0,0 @@
|
||||
## Adding Injection Queries
|
||||
|
||||
Writing language injection queries allows one to highlight a specific node as a different language.
|
||||
In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
|
||||
are a few Helix specific extensions that allow for more control.
|
||||
|
||||
An example of a simple query that would highlight all strings as bash in Nix:
|
||||
```scm
|
||||
((string_expression (string_fragment) @injection.content)
|
||||
(#set! injection.language "bash"))
|
||||
```
|
||||
Another example is this query, which highlights links in comments and keywords like "TODO", by reusing the dedicated "comment" language:
|
||||
```
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
```
|
||||
|
||||
## Capture Types
|
||||
|
||||
- `@injection.language` (standard):
|
||||
The captured node may contain the language name used to highlight the node captured by
|
||||
`@injection.content`.
|
||||
|
||||
- `@injection.content` (standard):
|
||||
Marks the content to be highlighted as the language captured with `@injection.language` _et al_.
|
||||
|
||||
- `@injection.filename` (extension):
|
||||
The captured node may contain a filename with a file-extension known to Helix,
|
||||
highlighting `@injection.content` as that language. This uses the language extensions defined in
|
||||
both the default languages.toml distributed with Helix, as well as user defined languages.
|
||||
|
||||
- `@injection.shebang` (extension):
|
||||
The captured node may contain a shebang used to choose a language to highlight as. This also uses
|
||||
the shebangs defined in the default and user `languages.toml`.
|
||||
|
||||
## Settings
|
||||
|
||||
- `injection.combined` (standard):
|
||||
Indicates that all the matching nodes in the tree should have their content parsed as one
|
||||
nested document.
|
||||
|
||||
- `injection.language` (standard):
|
||||
Forces the captured content to be highlighted as the given language
|
||||
|
||||
- `injection.include-children` (standard):
|
||||
Indicates that the content node’s entire text should be re-parsed, including the text of its child
|
||||
nodes. By default, child nodes’ text will be excluded from the injected document.
|
||||
|
||||
- `injection.include-unnamed-children` (extension):
|
||||
Same as `injection.include-children` but only for unnamed child nodes.
|
||||
|
||||
## Predicates
|
||||
|
||||
- `#eq?` (standard):
|
||||
The first argument (a capture) must be equal to the second argument
|
||||
(a capture or a string).
|
||||
|
||||
- `#match?` (standard):
|
||||
The first argument (a capture) must match the regex given in the
|
||||
second argument (a string).
|
||||
|
||||
- `#any-of?` (standard):
|
||||
The first argument (a capture) must be one of the other arguments (strings).
|
||||
|
||||
[upstream-docs]: https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#language-injection
|
@@ -1,132 +0,0 @@
|
||||
# Adding Rainbow Bracket Queries
|
||||
|
||||
Helix uses `rainbows.scm` tree-sitter query files to provide rainbow bracket
|
||||
functionality.
|
||||
|
||||
Tree-sitter queries are documented in the tree-sitter online documentation.
|
||||
If you're writing queries for the first time, be sure to check out the section
|
||||
on [syntax highlighting queries] and on [query syntax].
|
||||
|
||||
Rainbow queries have two captures: `@rainbow.scope` and `@rainbow.bracket`.
|
||||
`@rainbow.scope` should capture any node that increases the nesting level
|
||||
while `@rainbow.bracket` should capture any bracket nodes. Put another way:
|
||||
`@rainbow.scope` switches to the next rainbow color for all nodes in the tree
|
||||
under it while `@rainbow.bracket` paints captured nodes with the current
|
||||
rainbow color.
|
||||
|
||||
For an example, let's add rainbow queries for the tree-sitter query (TSQ)
|
||||
language itself. These queries will go into a
|
||||
`runtime/queries/tsq/rainbows.scm` file in the repository root.
|
||||
|
||||
First we'll add the `@rainbow.bracket` captures. TSQ only has parentheses and
|
||||
square brackets:
|
||||
|
||||
```tsq
|
||||
["(" ")" "[" "]"] @rainbow.bracket
|
||||
```
|
||||
|
||||
The ordering of the nodes within the alternation (square brackets) is not
|
||||
taken into consideration.
|
||||
|
||||
> Note: Why are these nodes quoted? Most syntax highlights capture text
|
||||
> surrounded by parentheses. These are _named nodes_ and correspond to the
|
||||
> names of rules in the grammar. Brackets are usually written in tree-sitter
|
||||
> grammars as literal strings, for example:
|
||||
>
|
||||
> ```js
|
||||
> {
|
||||
> // ...
|
||||
> arguments: seq("(", repeat($.argument), ")"),
|
||||
> // ...
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> Nodes written as literal strings in tree-sitter grammars may be captured
|
||||
> in queries with those same literal strings.
|
||||
|
||||
Then we'll add `@rainbow.scope` captures. The easiest way to do this is to
|
||||
view the `grammar.js` file in the tree-sitter grammar's repository. For TSQ,
|
||||
that file is [here][tsq grammar.js]. As we scroll down the `grammar.js`, we
|
||||
see that the `(alternation)`, (L36) `(group)` (L57), `(named_node)` (L59),
|
||||
`(predicate)` (L87) and `(wildcard_node)` (L97) nodes all contain literal
|
||||
parentheses or square brackets in their definitions. These nodes are all
|
||||
direct parents of brackets and happen to also be the nodes we want to change
|
||||
to the next rainbow color, so we capture them as `@rainbow.scope`.
|
||||
|
||||
```tsq
|
||||
[
|
||||
(group)
|
||||
(named_node)
|
||||
(wildcard_node)
|
||||
(predicate)
|
||||
(alternation)
|
||||
] @rainbow.scope
|
||||
```
|
||||
|
||||
This strategy works as a rule of thumb for most programming and configuration
|
||||
languages. Markup languages can be trickier and may take additional
|
||||
experimentation to find the correct nodes to use for scopes and brackets.
|
||||
|
||||
The `:tree-sitter-subtree` command shows the syntax tree under the primary
|
||||
selection in S-expression format and can be a useful tool for determining how
|
||||
to write a query.
|
||||
|
||||
### Properties
|
||||
|
||||
The `rainbow.include-children` property may be applied to `@rainbow.scope`
|
||||
captures. By default, all `@rainbow.bracket` captures must be direct descendant
|
||||
of a node captured with `@rainbow.scope` in a syntax tree in order to be
|
||||
highlighted. The `rainbow.include-children` property disables that check and
|
||||
allows `@rainbow.bracket` captures to be highlighted if they are direct or
|
||||
indirect descendants of some node captured with `@rainbow.scope`.
|
||||
|
||||
For example, this property is used in the HTML rainbow queries.
|
||||
|
||||
For a document like `<a>link</a>`, the syntax tree is:
|
||||
|
||||
```tsq
|
||||
(element ; <a>link</a>
|
||||
(start_tag ; <a>
|
||||
(tag_name)) ; a
|
||||
(text) ; link
|
||||
(end_tag ; </a>
|
||||
(tag_name))) ; a
|
||||
```
|
||||
|
||||
If we want to highlight the `<`, `>` and `</` nodes with rainbow colors, we
|
||||
capture them as `@rainbow.bracket`:
|
||||
|
||||
```tsq
|
||||
["<" ">" "</"] @rainbow.bracket
|
||||
```
|
||||
|
||||
And we capture `(element)` as `@rainbow.scope` because `(element)` nodes nest
|
||||
within each other: they increment the nesting level and switch to the next
|
||||
color in the rainbow.
|
||||
|
||||
```tsq
|
||||
(element) @rainbow.scope
|
||||
```
|
||||
|
||||
But this combination of `@rainbow.scope` and `@rainbow.bracket` will not
|
||||
highlight any nodes. `<`, `>` and `</` are children of the `(start_tag)` and
|
||||
`(end_tag)` nodes. We can't capture `(start_tag)` and `(end_tag)` as
|
||||
`@rainbow.scope` because they don't nest other elements. We can fix this case
|
||||
by removing the requirement that `<`, `>` and `</` are direct descendants of
|
||||
`(element)` using the `rainbow.include-children` property.
|
||||
|
||||
```tsq
|
||||
((element) @rainbow.scope
|
||||
(#set! rainbow.include-children))
|
||||
```
|
||||
|
||||
With this property set, `<`, `>`, and `</` will highlight with rainbow colors
|
||||
even though they aren't direct descendents of the `(element)` node.
|
||||
|
||||
`rainbow.include-children` is not necessary for the vast majority of programming
|
||||
languages. It is only necessary when the node that increments the nesting level
|
||||
(changes rainbow color) is not the direct parent of the bracket node.
|
||||
|
||||
[syntax highlighting queries]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#highlights
|
||||
[query syntax]: https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries
|
||||
[tsq grammar.js]: https://github.com/the-mikedavis/tree-sitter-tsq/blob/48b5e9f82ae0a4727201626f33a17f69f8e0ff86/grammar.js
|
@@ -1,34 +0,0 @@
|
||||
## Adding tags queries
|
||||
|
||||
See tree-sitter's documentation on [Code Navigation Systems] for more
|
||||
background on tags queries.
|
||||
|
||||
Helix provides LSP-like features such as document and workspace symbol pickers
|
||||
out-of-the-box for languages with `tags.scm` queries based on syntax trees. To
|
||||
be analyzed a language must have a tree-sitter grammar and a `tags.scm` query
|
||||
file which pattern matches interesting nodes from syntax trees.
|
||||
|
||||
Query files should be placed in `runtime/queries/{language}/tags.scm`
|
||||
when contributing to Helix. You may place these under your local runtime
|
||||
directory (`~/.config/helix/runtime` in Linux for example) for the sake of
|
||||
testing.
|
||||
|
||||
The following [captures][tree-sitter-captures] are recognized:
|
||||
|
||||
| Capture name |
|
||||
|--- |
|
||||
| `definition.class` |
|
||||
| `definition.constant` |
|
||||
| `definition.function` |
|
||||
| `definition.interface` |
|
||||
| `definition.macro` |
|
||||
| `definition.module` |
|
||||
| `definition.struct` |
|
||||
| `definition.type` |
|
||||
|
||||
[Example query files][example-queries] can be found in the Helix GitHub
|
||||
repository.
|
||||
|
||||
[Code Navigation Systems]: https://tree-sitter.github.io/tree-sitter/4-code-navigation.html
|
||||
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/index.html
|
||||
[example-queries]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+path%3A%2A%2A/tags.scm&type=Code
|
@@ -1,14 +1,14 @@
|
||||
## Adding textobject queries
|
||||
# Adding Textobject Queries
|
||||
|
||||
Helix supports textobjects that are language specific, such as functions, classes, etc.
|
||||
These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
|
||||
Textobjects that are language specific ([like functions, classes, etc][textobjects])
|
||||
require an accompanying tree-sitter grammar and a `textobjects.scm` query file
|
||||
to work properly. Tree-sitter allows us to query the source code syntax tree
|
||||
and capture specific parts of it. The queries are written in a lisp dialect.
|
||||
More information on how to write queries can be found in the [official tree-sitter
|
||||
documentation][tree-sitter-queries].
|
||||
|
||||
Query files should be placed in `runtime/queries/{language}/textobjects.scm`
|
||||
when contributing to Helix. Note that to test the query files locally you should put
|
||||
when contributing. Note that to test the query files locally you should put
|
||||
them under your local runtime directory (`~/.config/helix/runtime` on Linux
|
||||
for example).
|
||||
|
||||
@@ -20,22 +20,15 @@ The following [captures][tree-sitter-captures] are recognized:
|
||||
| `function.around` |
|
||||
| `class.inside` |
|
||||
| `class.around` |
|
||||
| `test.inside` |
|
||||
| `test.around` |
|
||||
| `parameter.inside` |
|
||||
| `parameter.around` |
|
||||
| `comment.inside` |
|
||||
| `comment.around` |
|
||||
| `entry.inside` |
|
||||
| `entry.around` |
|
||||
| `xml-element.inside` |
|
||||
| `xml-element.around` |
|
||||
|
||||
[Example query files][textobject-examples] can be found in the helix GitHub repository.
|
||||
|
||||
## Queries for textobject based navigation
|
||||
## Queries for Textobject Based Navigation
|
||||
|
||||
Tree-sitter based navigation in Helix is done using captures in the
|
||||
[Tree-sitter based navigation][textobjects-nav] is done using captures in the
|
||||
following order:
|
||||
|
||||
- `object.movement`
|
||||
@@ -43,10 +36,12 @@ following order:
|
||||
- `object.inside`
|
||||
|
||||
For example if a `function.around` capture has been already defined for a language
|
||||
in its `textobjects.scm` file, function navigation should also work automatically.
|
||||
in it's `textobjects.scm` file, function navigation should also work automatically.
|
||||
`function.movement` should be defined only if the node captured by `function.around`
|
||||
doesn't make sense in a navigation context.
|
||||
|
||||
[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html
|
||||
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/2-operators.html#capturing-nodes
|
||||
[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+path%3A%2A%2A/textobjects.scm&type=Code&ref=advsearch&l=&l=
|
||||
[textobjects]: ../usage.md#textobjects
|
||||
[textobjects-nav]: ../usage.md#tree-sitter-textobject-based-navigation
|
||||
[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
|
||||
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
|
||||
[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
|
||||
|
1
book/src/hooks.md
Normal file
1
book/src/hooks.md
Normal file
@@ -0,0 +1 @@
|
||||
# Hooks
|
@@ -1,24 +1,62 @@
|
||||
# Installing Helix
|
||||
# Installation
|
||||
|
||||
The typical way to install Helix is via [your operating system's package manager](./package-managers.md).
|
||||
We provide pre-built binaries on the [GitHub Releases page](https://github.com/helix-editor/helix/releases).
|
||||
|
||||
Note that:
|
||||
[](https://repology.org/project/helix/versions)
|
||||
|
||||
- To get the latest nightly version of Helix, you need to
|
||||
[build from source](./building-from-source.md).
|
||||
## OSX
|
||||
|
||||
- To take full advantage of Helix, install the language servers for your
|
||||
preferred programming languages. See the
|
||||
[wiki](https://github.com/helix-editor/helix/wiki/Language-Server-Configurations)
|
||||
for instructions.
|
||||
A Homebrew tap is available:
|
||||
|
||||
## Pre-built binaries
|
||||
```
|
||||
brew tap helix-editor/helix
|
||||
brew install helix
|
||||
```
|
||||
|
||||
Download pre-built binaries from the [GitHub Releases page](https://github.com/helix-editor/helix/releases).
|
||||
The tarball contents include an `hx` binary and a `runtime` directory.
|
||||
To set up Helix:
|
||||
## Linux
|
||||
|
||||
1. Add the `hx` binary to your system's `$PATH` to allow it to be used from the command line.
|
||||
2. Copy the `runtime` directory to a location that `hx` searches for runtime files. A typical location on Linux/macOS is `~/.config/helix/runtime`.
|
||||
### NixOS
|
||||
|
||||
To see the runtime directories that `hx` searches, run `hx --health`. If necessary, you can override the default runtime location by setting the `HELIX_RUNTIME` environment variable.
|
||||
A [flake](https://nixos.wiki/wiki/Flakes) containing the package is available in
|
||||
the project root. The flake can also be used to spin up a reproducible development
|
||||
shell for working on Helix with `nix develop`.
|
||||
|
||||
Flake outputs are cached for each push to master using
|
||||
[Cachix](https://www.cachix.org/). With Cachix
|
||||
[installed](https://docs.cachix.org/installation), `cachix use helix` will
|
||||
configure Nix to use cached outputs when possible.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Releases are available in the `community` repository.
|
||||
|
||||
A [helix-git](https://aur.archlinux.org/packages/helix-git/) package is also available on the AUR, which builds the master branch.
|
||||
|
||||
### Fedora Linux
|
||||
|
||||
You can install the COPR package for Helix via
|
||||
|
||||
```
|
||||
sudo dnf copr enable varlad/helix
|
||||
sudo dnf install helix
|
||||
```
|
||||
|
||||
## Build from source
|
||||
|
||||
```
|
||||
git clone https://github.com/helix-editor/helix
|
||||
cd helix
|
||||
cargo install --path helix-term
|
||||
```
|
||||
|
||||
This will install the `hx` binary to `$HOME/.cargo/bin`.
|
||||
|
||||
Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the
|
||||
config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden
|
||||
via the `HELIX_RUNTIME` environment variable.
|
||||
|
||||
## Building tree-sitter grammars
|
||||
|
||||
Tree-sitter grammars must be fetched and compiled if not pre-packaged.
|
||||
Fetch grammars with `hx --grammar fetch` (requires `git`) and compile them
|
||||
with `hx --grammar build` (requires a C compiler).
|
||||
|
@@ -1,36 +0,0 @@
|
||||
## Using the jumplist
|
||||
|
||||
To help with quick navigation, Helix maintains a list of "jumps" called the jumplist.
|
||||
Whenever you make a significant movement (see next section), Helix stores your selections from before the move as a jump.
|
||||
A jump serves as a kind of checkpoint, allowing you to jump to a separate location, make edits, and return to where you were with your previous selections.
|
||||
This way, the jumplist tracks both your previous location and your selections.
|
||||
You can manually save a jump by using `Ctrl-s`.
|
||||
To jump backward in the jumplist, use `Ctrl-o`; to go forward, use `Ctrl-i`. To view and select from the full jumplist, use `Space-j` to open the jumplist picker.
|
||||
|
||||
### What makes a jump
|
||||
The following is a non-exhaustive list of which actions add a jump to the jumplist:
|
||||
- Switching buffers
|
||||
- Using the buffer picker, going to the next/previous buffer
|
||||
- Going to the last accessed/modified file
|
||||
- Making a new file (`:new FILE`)
|
||||
- Opening a file (`:open FILE`)
|
||||
- Includes `:log-open`, `:config-open`, `:config-open-workspace`, `:tutor`
|
||||
- Navigating by pickers, global search, or the file explorer
|
||||
- `goto_file` (`gf`)
|
||||
- Big in-file movements
|
||||
- `select_regex` (`s`)
|
||||
- `split_regex` (`S`)
|
||||
- `search` (`/`)
|
||||
- `keep_selections` and `remove_selections` (`K` and `<A-K>`)
|
||||
- `goto_file_start` (`gg`)
|
||||
- `goto_file_end`
|
||||
- `goto_last_line` (`ge`)
|
||||
- `:goto 123` / `:123` / `123G`
|
||||
- `goto_definition` (`gd`)
|
||||
- `goto_declaration` (`gD`)
|
||||
- `goto_type_definition` (`gy`)
|
||||
- `goto_reference` (`gr`)
|
||||
- Other
|
||||
- `Ctrl-s` manually creates a jump
|
||||
- Trying to close a modified buffer can switch you to that buffer and create a jump
|
||||
- The debugger can create jumps as you jump stack frames
|
@@ -1,47 +1,19 @@
|
||||
## Keymap
|
||||
# Keymap
|
||||
|
||||
- [Normal mode](#normal-mode)
|
||||
- [Movement](#movement)
|
||||
- [Changes](#changes)
|
||||
- [Shell](#shell)
|
||||
- [Selection manipulation](#selection-manipulation)
|
||||
- [Search](#search)
|
||||
- [Minor modes](#minor-modes)
|
||||
- [View mode](#view-mode)
|
||||
- [Goto mode](#goto-mode)
|
||||
- [Match mode](#match-mode)
|
||||
- [Window mode](#window-mode)
|
||||
- [Space mode](#space-mode)
|
||||
- [Popup](#popup)
|
||||
- [Completion Menu](#completion-menu)
|
||||
- [Signature-help Popup](#signature-help-popup)
|
||||
- [Unimpaired](#unimpaired)
|
||||
- [Insert mode](#insert-mode)
|
||||
- [Select / extend mode](#select--extend-mode)
|
||||
- [Picker](#picker)
|
||||
- [Prompt](#prompt)
|
||||
|
||||
> 💡 Mappings marked (**LSP**) require an active language server for the file.
|
||||
|
||||
> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the file type.
|
||||
|
||||
> ⚠️ Some terminals' default key mappings conflict with Helix's. If any of the mappings described on this page do not work as expected, check your terminal's mappings to ensure they do not conflict. See the [wiki](https://github.com/helix-editor/helix/wiki/Terminal-Support) for known conflicts.
|
||||
- Mappings marked (**LSP**) require an active language server for the file.
|
||||
- Mappings marked (**TS**) require a tree-sitter grammar for the filetype.
|
||||
|
||||
## Normal mode
|
||||
|
||||
Normal mode is the default mode when you launch helix. You can return to it from other modes by pressing the `Escape` key.
|
||||
|
||||
### Movement
|
||||
|
||||
> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
|
||||
|
||||
> Hereafter, `<n>` represents an integer by typing a sequence of digits.
|
||||
> NOTE: Unlike vim, `f`, `F`, `t` and `T` are not confined to the current line.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `h`, `Left` | Move left | `move_char_left` |
|
||||
| `j`, `Down` | Move down | `move_visual_line_down` |
|
||||
| `k`, `Up` | Move up | `move_visual_line_up` |
|
||||
| `j`, `Down` | Move down | `move_line_down` |
|
||||
| `k`, `Up` | Move up | `move_line_up` |
|
||||
| `l`, `Right` | Move right | `move_char_right` |
|
||||
| `w` | Move next word start | `move_next_word_start` |
|
||||
| `b` | Move previous word start | `move_prev_word_start` |
|
||||
@@ -49,18 +21,18 @@ Normal mode is the default mode when you launch helix. You can return to it from
|
||||
| `W` | Move next WORD start | `move_next_long_word_start` |
|
||||
| `B` | Move previous WORD start | `move_prev_long_word_start` |
|
||||
| `E` | Move next WORD end | `move_next_long_word_end` |
|
||||
| `t` | Find till next char | `find_till_char` |
|
||||
| `t` | Find 'till next char | `find_till_char` |
|
||||
| `f` | Find next char | `find_next_char` |
|
||||
| `T` | Find till previous char | `till_prev_char` |
|
||||
| `T` | Find 'till previous char | `till_prev_char` |
|
||||
| `F` | Find previous char | `find_prev_char` |
|
||||
| `<n>G`, `<n>gg` | Go to line number `<n>` | `goto_line` |
|
||||
| `Alt-.` | Repeat last motion (`f`, `t`, `m`, `[` or `]`) | `repeat_last_motion` |
|
||||
| `G` | Go to line number `<n>` | `goto_line` |
|
||||
| `Alt-.` | Repeat last motion (`f`, `t` or `m`) | `repeat_last_motion` |
|
||||
| `Home` | Move to the start of the line | `goto_line_start` |
|
||||
| `End` | Move to the end of the line | `goto_line_end` |
|
||||
| `Ctrl-b`, `PageUp` | Move page up | `page_up` |
|
||||
| `Ctrl-f`, `PageDown` | Move page down | `page_down` |
|
||||
| `Ctrl-u` | Move cursor and page half page up | `page_cursor_half_up` |
|
||||
| `Ctrl-d` | Move cursor and page half page down | `page_cursor_half_down` |
|
||||
| `Ctrl-u` | Move half page up | `half_page_up` |
|
||||
| `Ctrl-d` | Move half page down | `half_page_down` |
|
||||
| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
|
||||
| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
|
||||
| `Ctrl-s` | Save the current selection to the jumplist | `save_selection` |
|
||||
@@ -76,11 +48,11 @@ Normal mode is the default mode when you launch helix. You can return to it from
|
||||
| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` |
|
||||
| `i` | Insert before selection | `insert_mode` |
|
||||
| `a` | Insert after selection (append) | `append_mode` |
|
||||
| `I` | Insert at the start of the line | `insert_at_line_start` |
|
||||
| `A` | Insert at the end of the line | `insert_at_line_end` |
|
||||
| `I` | Insert at the start of the line | `prepend_to_line` |
|
||||
| `A` | Insert at the end of the line | `append_to_line` |
|
||||
| `o` | Open new line below selection | `open_below` |
|
||||
| `O` | Open new line above selection | `open_above` |
|
||||
| `.` | Repeat last insert | N/A |
|
||||
| `.` | Repeat last change | N/A |
|
||||
| `u` | Undo change | `undo` |
|
||||
| `U` | Redo change | `redo` |
|
||||
| `Alt-u` | Move backward in history | `earlier` |
|
||||
@@ -91,7 +63,7 @@ Normal mode is the default mode when you launch helix. You can return to it from
|
||||
| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
|
||||
| `>` | Indent selection | `indent` |
|
||||
| `<` | Unindent selection | `unindent` |
|
||||
| `=` | Format selection (**LSP**) | `format_selections` |
|
||||
| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
|
||||
| `d` | Delete selection | `delete_selection` |
|
||||
| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
|
||||
| `c` | Change selection (delete and enter insert mode) | `change_selection` |
|
||||
@@ -119,8 +91,6 @@ Normal mode is the default mode when you launch helix. You can return to it from
|
||||
| `s` | Select all regex matches inside selections | `select_regex` |
|
||||
| `S` | Split selection into subselections on regex matches | `split_selection` |
|
||||
| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
|
||||
| `Alt-minus` | Merge selections | `merge_selections` |
|
||||
| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
|
||||
| `&` | Align selection in columns | `align_selections` |
|
||||
| `_` | Trim whitespace from the selection | `trim_selections` |
|
||||
| `;` | Collapse selection onto a single cursor | `collapse_selection` |
|
||||
@@ -135,26 +105,20 @@ Normal mode is the default mode when you launch helix. You can return to it from
|
||||
| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
|
||||
| `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` |
|
||||
| `%` | Select entire file | `select_all` |
|
||||
| `x` | Select current line, if already selected, extend to next line | `extend_line_below` |
|
||||
| `x` | Select current line, if already selected, extend to next line | `extend_line` |
|
||||
| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
|
||||
| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
|
||||
| `J` | Join lines inside selection | `join_selections` |
|
||||
| `Alt-J` | Join lines inside selection and select the inserted space | `join_selections_space` |
|
||||
| `K` | Keep selections matching the regex | `keep_selections` |
|
||||
| `Alt-K` | Remove selections matching the regex | `remove_selections` |
|
||||
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
|
||||
| `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
|
||||
| `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
|
||||
| `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
|
||||
| `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
|
||||
| `Alt-a` | Select all sibling nodes in syntax tree (**TS**) | `select_all_siblings` |
|
||||
| `Alt-I`, `Alt-Shift-down`| Select all children nodes in syntax tree (**TS**) | `select_all_children` |
|
||||
| `Alt-e` | Move to end of parent node in syntax tree (**TS**) | `move_parent_node_end` |
|
||||
| `Alt-b` | Move to start of parent node in syntax tree (**TS**) | `move_parent_node_start` |
|
||||
| `Alt-k`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
|
||||
| `Alt-j`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
|
||||
| `Alt-h`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
|
||||
| `Alt-l`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
|
||||
|
||||
### Search
|
||||
|
||||
Search commands all operate on the `/` register by default. To use a different register, use `"<char>`.
|
||||
Search commands all operate on the `/` register by default. Use `"<char>` to operate on a different one.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
@@ -162,8 +126,7 @@ Search commands all operate on the `/` register by default. To use a different r
|
||||
| `?` | Search for previous pattern | `rsearch` |
|
||||
| `n` | Select next search match | `search_next` |
|
||||
| `N` | Select previous search match | `search_prev` |
|
||||
| `*` | Use current selection as the search pattern, automatically wrapping with `\b` on word boundaries | `search_selection_detect_word_boundaries` |
|
||||
| `Alt-*` | Use current selection as the search pattern | `search_selection` |
|
||||
| `*` | Use current selection as the search pattern | `search_selection` |
|
||||
|
||||
### Minor modes
|
||||
|
||||
@@ -180,17 +143,12 @@ These sub-modes are accessible from normal mode and typically switch back to nor
|
||||
| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
|
||||
| `Space` | Enter [space mode](#space-mode) | N/A |
|
||||
|
||||
These modes (except command mode) can be configured by
|
||||
[remapping keys](https://docs.helix-editor.com/remapping.html#minor-modes).
|
||||
|
||||
#### View mode
|
||||
|
||||
Accessed by typing `z` in [normal mode](#normal-mode).
|
||||
|
||||
View mode is intended for scrolling and manipulating the view without changing
|
||||
the selection. The "sticky" variant of this mode (accessed by typing `Z` in
|
||||
normal mode) is persistent and can be exited using the escape key. This is
|
||||
useful when you're simply looking over text and not actively editing it.
|
||||
the selection. The "sticky" variant of this mode is persistent; use the Escape
|
||||
key to return to normal mode after usage (useful when you're simply looking
|
||||
over text and not actively editing it).
|
||||
|
||||
|
||||
| Key | Description | Command |
|
||||
@@ -203,23 +161,18 @@ useful when you're simply looking over text and not actively editing it.
|
||||
| `k`, `up` | Scroll the view upwards | `scroll_up` |
|
||||
| `Ctrl-f`, `PageDown` | Move page down | `page_down` |
|
||||
| `Ctrl-b`, `PageUp` | Move page up | `page_up` |
|
||||
| `Ctrl-u` | Move cursor and page half page up | `page_cursor_half_up` |
|
||||
| `Ctrl-d` | Move cursor and page half page down | `page_cursor_half_down` |
|
||||
| `Ctrl-d` | Move half page down | `half_page_down` |
|
||||
| `Ctrl-u` | Move half page up | `half_page_up` |
|
||||
|
||||
#### Goto mode
|
||||
|
||||
Accessed by typing `g` in [normal mode](#normal-mode).
|
||||
|
||||
Jumps to various locations.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `<n>g`| Go to line number `<n>` | `goto_file_start` |
|
||||
| `g` | Go to the start of the file | `goto_file_start` |
|
||||
| <code><n>|</code> | Go to column number `<n>` | `goto_column` |
|
||||
| <code>|</code> | Go to the start of line | `goto_column` |
|
||||
| `g` | Go to line number `<n>` else start of file | `goto_file_start` |
|
||||
| `e` | Go to the end of the file | `goto_last_line` |
|
||||
| `f` | Go to files in the selections | `goto_file` |
|
||||
| `f` | Go to files in the selection | `goto_file` |
|
||||
| `h` | Go to the start of the line | `goto_line_start` |
|
||||
| `l` | Go to the end of the line | `goto_line_end` |
|
||||
| `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` |
|
||||
@@ -235,15 +188,12 @@ Jumps to various locations.
|
||||
| `n` | Go to next buffer | `goto_next_buffer` |
|
||||
| `p` | Go to previous buffer | `goto_previous_buffer` |
|
||||
| `.` | Go to last modification in current file | `goto_last_modification` |
|
||||
| `j` | Move down textual (instead of visual) line | `move_line_down` |
|
||||
| `k` | Move up textual (instead of visual) line | `move_line_up` |
|
||||
| `w` | Show labels at each word and select the word that belongs to the entered labels | `goto_word` |
|
||||
|
||||
#### Match mode
|
||||
|
||||
Accessed by typing `m` in [normal mode](#normal-mode).
|
||||
|
||||
Please refer to the relevant sections for detailed explanations about [surround](./surround.md) and [textobjects](./textobjects.md).
|
||||
Enter this mode using `m` from normal mode. See the relavant section
|
||||
in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround)
|
||||
and [textobject](./usage.md#textobject) usage.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
@@ -258,189 +208,115 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
|
||||
|
||||
#### Window mode
|
||||
|
||||
Accessed by typing `Ctrl-w` in [normal mode](#normal-mode).
|
||||
|
||||
This layer is similar to Vim keybindings as Kakoune does not support windows.
|
||||
This layer is similar to vim keybindings as kakoune does not support window.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ------------- | ------- |
|
||||
| `w`, `Ctrl-w` | Switch to next window | `rotate_view` |
|
||||
| `v`, `Ctrl-v` | Vertical right split | `vsplit` |
|
||||
| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` |
|
||||
| `f` | Go to files in the selections in horizontal splits | `goto_file` |
|
||||
| `F` | Go to files in the selections in vertical splits | `goto_file` |
|
||||
| `f` | Go to files in the selection in horizontal splits | `goto_file` |
|
||||
| `F` | Go to files in the selection in vertical splits | `goto_file` |
|
||||
| `h`, `Ctrl-h`, `Left` | Move to left split | `jump_view_left` |
|
||||
| `j`, `Ctrl-j`, `Down` | Move to split below | `jump_view_down` |
|
||||
| `k`, `Ctrl-k`, `Up` | Move to split above | `jump_view_up` |
|
||||
| `l`, `Ctrl-l`, `Right` | Move to right split | `jump_view_right` |
|
||||
| `q`, `Ctrl-q` | Close current window | `wclose` |
|
||||
| `o`, `Ctrl-o` | Only keep the current window, closing all the others | `wonly` |
|
||||
| `H` | Swap window to the left | `swap_view_left` |
|
||||
| `J` | Swap window downwards | `swap_view_down` |
|
||||
| `K` | Swap window upwards | `swap_view_up` |
|
||||
| `L` | Swap window to the right | `swap_view_right` |
|
||||
|
||||
#### Space mode
|
||||
|
||||
Accessed by typing `Space` in [normal mode](#normal-mode).
|
||||
|
||||
This layer is a kludge of mappings, mostly pickers.
|
||||
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `f` | Open file picker at LSP workspace root | `file_picker` |
|
||||
| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
|
||||
| `f` | Open file picker | `file_picker` |
|
||||
| `b` | Open buffer picker | `buffer_picker` |
|
||||
| `j` | Open jumplist picker | `jumplist_picker` |
|
||||
| `g` | Open changed file picker | `changed_file_picker` |
|
||||
| `G` | Debug (experimental) | N/A |
|
||||
| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
|
||||
| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
|
||||
| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
|
||||
| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
|
||||
| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
|
||||
| `r` | Rename symbol (**LSP**) | `rename_symbol` |
|
||||
| `a` | Apply code action (**LSP**) | `code_action` |
|
||||
| `h` | Select symbol references (**LSP**) | `select_references_to_symbol_under_cursor` |
|
||||
| `'` | Open last fuzzy picker | `last_picker` |
|
||||
| `w` | Enter [window mode](#window-mode) | N/A |
|
||||
| `c` | Comment/uncomment selections | `toggle_comments` |
|
||||
| `C` | Block comment/uncomment selections | `toggle_block_comments` |
|
||||
| `Alt-c` | Line comment/uncomment selections | `toggle_line_comments` |
|
||||
| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
|
||||
| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
|
||||
| `y` | Yank selections to clipboard | `yank_to_clipboard` |
|
||||
| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
|
||||
| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
|
||||
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
|
||||
| `/` | Global search in workspace folder | `global_search` |
|
||||
| `?` | Open command palette | `command_palette` |
|
||||
|
||||
> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
|
||||
> TIP: Global search displays results in a fuzzy picker, use `space + '` to bring it back up after opening a file.
|
||||
|
||||
##### Popup
|
||||
|
||||
Displays documentation for item under cursor. Remapping currently not supported.
|
||||
Displays documentation for item under cursor.
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `Ctrl-u` | Scroll up |
|
||||
| `Ctrl-d` | Scroll down |
|
||||
|
||||
##### Completion Menu
|
||||
|
||||
Displays documentation for the selected completion item. Remapping currently not supported.
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `Shift-Tab`, `Ctrl-p`, `Up` | Previous entry |
|
||||
| `Tab`, `Ctrl-n`, `Down` | Next entry |
|
||||
| `Enter` | Close menu and accept completion |
|
||||
| `Ctrl-c` | Close menu and reject completion |
|
||||
|
||||
Any other keypresses result in the completion being accepted.
|
||||
|
||||
##### Signature-help Popup
|
||||
|
||||
Displays the signature of the selected completion item. Remapping currently not supported.
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `Alt-p` | Previous signature |
|
||||
| `Alt-n` | Next signature |
|
||||
|
||||
#### Unimpaired
|
||||
|
||||
These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
|
||||
Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` |
|
||||
| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
|
||||
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
|
||||
| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` |
|
||||
| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` |
|
||||
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
|
||||
| `]f` | Go to next function (**TS**) | `goto_next_function` |
|
||||
| `[f` | Go to previous function (**TS**) | `goto_prev_function` |
|
||||
| `]t` | Go to next type definition (**TS**) | `goto_next_class` |
|
||||
| `[t` | Go to previous type definition (**TS**) | `goto_prev_class` |
|
||||
| `]c` | Go to next class (**TS**) | `goto_next_class` |
|
||||
| `[c` | Go to previous class (**TS**) | `goto_prev_class` |
|
||||
| `]a` | Go to next argument/parameter (**TS**) | `goto_next_parameter` |
|
||||
| `[a` | Go to previous argument/parameter (**TS**) | `goto_prev_parameter` |
|
||||
| `]c` | Go to next comment (**TS**) | `goto_next_comment` |
|
||||
| `[c` | Go to previous comment (**TS**) | `goto_prev_comment` |
|
||||
| `]T` | Go to next test (**TS**) | `goto_next_test` |
|
||||
| `[T` | Go to previous test (**TS**) | `goto_prev_test` |
|
||||
| `]p` | Go to next paragraph | `goto_next_paragraph` |
|
||||
| `[p` | Go to previous paragraph | `goto_prev_paragraph` |
|
||||
| `]g` | Go to next change | `goto_next_change` |
|
||||
| `[g` | Go to previous change | `goto_prev_change` |
|
||||
| `]G` | Go to last change | `goto_last_change` |
|
||||
| `[G` | Go to first change | `goto_first_change` |
|
||||
| `[x` | Go to next (X)HTML element | `goto_next_xml_element` |
|
||||
| `]x` | Go to previous (X)HTML element | `goto_prev_xml_element` |
|
||||
| `]Space` | Add newline below | `add_newline_below` |
|
||||
| `[Space` | Add newline above | `add_newline_above` |
|
||||
| `]o` | Go to next comment (**TS**) | `goto_next_comment` |
|
||||
| `[o` | Go to previous comment (**TS**) | `goto_prev_comment` |
|
||||
| `[space` | Add newline above | `add_newline_above` |
|
||||
| `]space` | Add newline below | `add_newline_below` |
|
||||
|
||||
## Insert mode
|
||||
## Insert Mode
|
||||
|
||||
Accessed by typing `i` in [normal mode](#normal-mode).
|
||||
|
||||
Insert mode bindings are minimal by default. Helix is designed to
|
||||
be a modal editor, and this is reflected in the user experience and internal
|
||||
mechanics. Changes to the text are only saved for undos when
|
||||
escaping from insert mode to normal mode.
|
||||
|
||||
> 💡 New users are strongly encouraged to learn the modal editing paradigm
|
||||
> to get the smoothest experience.
|
||||
We support many readline/emacs style bindings in insert mode for
|
||||
convenience. These can be helpful for making simple modifications
|
||||
without escaping to normal mode, but beware that you will not have an
|
||||
undo-able "save point" until you return to normal mode.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `Escape` | Switch to normal mode | `normal_mode` |
|
||||
| `Ctrl-s` | Commit undo checkpoint | `commit_undo_checkpoint` |
|
||||
| `Ctrl-x` | Autocomplete | `completion` |
|
||||
| `Ctrl-r` | Insert a register content | `insert_register` |
|
||||
| `Ctrl-w`, `Alt-Backspace` | Delete previous word | `delete_word_backward` |
|
||||
| `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` |
|
||||
| `Alt-d` | Delete next word | `delete_word_forward` |
|
||||
| `Alt-b`, `Alt-Left` | Backward a word | `move_prev_word_end` |
|
||||
| `Ctrl-b`, `Left` | Backward a char | `move_char_left` |
|
||||
| `Alt-f`, `Alt-Right` | Forward a word | `move_next_word_start` |
|
||||
| `Ctrl-f`, `Right` | Forward a char | `move_char_right` |
|
||||
| `Ctrl-e`, `End` | Move to line end | `goto_line_end_newline` |
|
||||
| `Ctrl-a`, `Home` | Move to line start | `goto_line_start` |
|
||||
| `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
|
||||
| `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
|
||||
| `Ctrl-h`, `Backspace`, `Shift-Backspace` | Delete previous char | `delete_char_backward` |
|
||||
| `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` |
|
||||
| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
|
||||
|
||||
These keys are not recommended, but are included for new users less familiar
|
||||
with modal editors.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `Up` | Move to previous line | `move_line_up` |
|
||||
| `Down` | Move to next line | `move_line_down` |
|
||||
| `Left` | Backward a char | `move_char_left` |
|
||||
| `Right` | Forward a char | `move_char_right` |
|
||||
| `Backspace`, `Ctrl-h` | Delete previous char | `delete_char_backward` |
|
||||
| `Delete`, `Ctrl-d` | Delete previous char | `delete_char_forward` |
|
||||
| `Ctrl-p`, `Up` | Move to previous line | `move_line_up` |
|
||||
| `Ctrl-n`, `Down` | Move to next line | `move_line_down` |
|
||||
| `PageUp` | Move one page up | `page_up` |
|
||||
| `PageDown` | Move one page down | `page_down` |
|
||||
| `Home` | Move to line start | `goto_line_start` |
|
||||
| `End` | Move to line end | `goto_line_end_newline` |
|
||||
|
||||
As you become more comfortable with modal editing, you may want to disable some
|
||||
insert mode bindings. You can do this by editing your `config.toml` file.
|
||||
|
||||
```toml
|
||||
[keys.insert]
|
||||
up = "no_op"
|
||||
down = "no_op"
|
||||
left = "no_op"
|
||||
right = "no_op"
|
||||
pageup = "no_op"
|
||||
pagedown = "no_op"
|
||||
home = "no_op"
|
||||
end = "no_op"
|
||||
```
|
||||
| `Alt->` | Go to end of buffer | `goto_file_end` |
|
||||
| `Alt-<` | Go to start of buffer | `goto_file_start` |
|
||||
|
||||
## Select / extend mode
|
||||
|
||||
Accessed by typing `v` in [normal mode](#normal-mode).
|
||||
|
||||
Select mode echoes Normal mode, but changes any movements to extend
|
||||
This mode echoes Normal mode, but changes any movements to extend
|
||||
selections rather than replace them. Goto motions are also changed to
|
||||
extend, so that `vgl`, for example, extends the selection to the end of
|
||||
extend, so that `vgl` for example extends the selection to the end of
|
||||
the line.
|
||||
|
||||
Search is also affected. By default, `n` and `N` will remove the current
|
||||
@@ -449,50 +325,47 @@ mode before pressing `n` or `N` makes it possible to keep the current
|
||||
selection. Toggling it on and off during your iterative searching allows
|
||||
you to selectively add search terms to your selections.
|
||||
|
||||
## Picker
|
||||
# Picker
|
||||
|
||||
Keys to use within picker. Remapping currently not supported.
|
||||
See the documentation page on [pickers](./pickers.md) for more info.
|
||||
[Prompt](#prompt) keybinds also work in pickers, except where they conflict with picker keybinds.
|
||||
|
||||
| Key | Description |
|
||||
| ----- | ------------- |
|
||||
| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
|
||||
| `Tab`, `Down`, `Ctrl-n` | Next entry |
|
||||
| `PageUp`, `Ctrl-u` | Page up |
|
||||
| `PageDown`, `Ctrl-d` | Page down |
|
||||
| `Up`, `Ctrl-k`, `Ctrl-p` | Previous entry |
|
||||
| `PageUp`, `Ctrl-b` | Page up |
|
||||
| `Down`, `Ctrl-j`, `Ctrl-n` | Next entry |
|
||||
| `PageDown`, `Ctrl-f` | Page down |
|
||||
| `Home` | Go to first entry |
|
||||
| `End` | Go to last entry |
|
||||
| `Ctrl-space` | Filter options |
|
||||
| `Enter` | Open selected |
|
||||
| `Alt-Enter` | Open selected in the background without closing the picker |
|
||||
| `Ctrl-s` | Open horizontally |
|
||||
| `Ctrl-v` | Open vertically |
|
||||
| `Ctrl-t` | Toggle preview |
|
||||
| `Escape`, `Ctrl-c` | Close picker |
|
||||
|
||||
## Prompt
|
||||
# Prompt
|
||||
|
||||
Keys to use within prompt, Remapping currently not supported.
|
||||
|
||||
| Key | Description |
|
||||
| ----- | ------------- |
|
||||
| `Escape`, `Ctrl-c` | Close prompt |
|
||||
| `Alt-b`, `Ctrl-Left` | Backward a word |
|
||||
| `Alt-b`, `Alt-Left` | Backward a word |
|
||||
| `Ctrl-b`, `Left` | Backward a char |
|
||||
| `Alt-f`, `Ctrl-Right` | Forward a word |
|
||||
| `Alt-f`, `Alt-Right` | Forward a word |
|
||||
| `Ctrl-f`, `Right` | Forward a char |
|
||||
| `Ctrl-e`, `End` | Move prompt end |
|
||||
| `Ctrl-a`, `Home` | Move prompt start |
|
||||
| `Ctrl-w`, `Alt-Backspace`, `Ctrl-Backspace` | Delete previous word |
|
||||
| `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word |
|
||||
| `Ctrl-w` | Delete previous word |
|
||||
| `Alt-d` | Delete next word |
|
||||
| `Ctrl-u` | Delete to start of line |
|
||||
| `Ctrl-k` | Delete to end of line |
|
||||
| `Backspace`, `Ctrl-h`, `Shift-Backspace` | Delete previous char |
|
||||
| `Delete`, `Ctrl-d` | Delete next char |
|
||||
| `backspace`, `Ctrl-h` | Delete previous char |
|
||||
| `delete`, `Ctrl-d` | Delete next char |
|
||||
| `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
|
||||
| `Ctrl-p`, `Up` | Select previous history |
|
||||
| `Ctrl-n`, `Down` | Select next history |
|
||||
| `Ctrl-r` | Insert the content of the register selected by following input char |
|
||||
| `Tab` | Select next completion item |
|
||||
| `BackTab` | Select previous completion item |
|
||||
| `Enter` | Open selected |
|
||||
|
||||
|
@@ -1,16 +1,10 @@
|
||||
## Language Support
|
||||
# Language Support
|
||||
|
||||
The following languages and Language Servers are supported. To use
|
||||
Language Server features, you must first [configure][lsp-config-wiki] the
|
||||
appropriate Language Server.
|
||||
|
||||
You can check the language support in your installed helix version with `hx --health`.
|
||||
|
||||
Also see the [Language Configuration][lang-config] docs and the [Adding
|
||||
Languages][adding-languages] guide for more language configuration information.
|
||||
For more information like arguments passed to default LSP server,
|
||||
extensions assosciated with a filetype, custom LSP settings, filetype
|
||||
specific indent settings, etc see the default
|
||||
[`languages.toml`][languages.toml] file.
|
||||
|
||||
{{#include ./generated/lang-support.md}}
|
||||
|
||||
[lsp-config-wiki]: https://github.com/helix-editor/helix/wiki/Language-Server-Configurations
|
||||
[lang-config]: ./languages.md
|
||||
[adding-languages]: ./guides/adding_languages.md
|
||||
[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml
|
||||
|
@@ -1,249 +1,34 @@
|
||||
## Languages
|
||||
# Languages
|
||||
|
||||
Language-specific settings and settings for language servers are configured
|
||||
in `languages.toml` files.
|
||||
Language-specific settings and settings for particular language servers can be configured in a `languages.toml` file placed in your [configuration directory](./configuration.md). Helix actually uses two `languages.toml` files, the [first one](https://github.com/helix-editor/helix/blob/master/languages.toml) is in the main helix repository; it contains the default settings for each language and is included in the helix binary at compile time. Users who want to see the available settings and options can either reference the helix repo's `languages.toml` file, or consult the table in the [adding languages](./guides/adding_languages.md) section.
|
||||
|
||||
## `languages.toml` files
|
||||
|
||||
There are three possible locations for a `languages.toml` file:
|
||||
|
||||
1. In the Helix source code, which lives in the
|
||||
[Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
|
||||
It provides the default configurations for languages and language servers.
|
||||
|
||||
2. In your [configuration directory](./configuration.md). This overrides values
|
||||
from the built-in language configuration. For example, to disable
|
||||
auto-formatting for Rust:
|
||||
Changes made to the `languages.toml` file in a user's [configuration directory](./configuration.md) are merged with helix's defaults on start-up, such that a user's settings will take precedence over defaults in the event of a collision. For example, the default `languages.toml` sets rust's `auto-format` to `true`. If a user wants to disable auto-format, they can change the `languages.toml` in their [configuration directory](./configuration.md) to make the rust entry read like the example below; the new key/value pair `auto-format = false` will override the default when the two sets of settings are merged on start-up:
|
||||
|
||||
```toml
|
||||
# in <config_dir>/helix/languages.toml
|
||||
|
||||
[language-server.mylang-lsp]
|
||||
command = "mylang-lsp"
|
||||
|
||||
[[language]]
|
||||
name = "rust"
|
||||
auto-format = false
|
||||
```
|
||||
|
||||
3. In a `.helix` folder in your project. Language configuration may also be
|
||||
overridden local to a project by creating a `languages.toml` file in a
|
||||
`.helix` folder. Its settings will be merged with the language configuration
|
||||
in the configuration directory and the built-in configuration.
|
||||
## Tree-sitter grammars
|
||||
|
||||
## Language configuration
|
||||
|
||||
Each language is configured by adding a `[[language]]` section to a
|
||||
`languages.toml` file. For example:
|
||||
Tree-sitter grammars can also be configured in `languages.toml`:
|
||||
|
||||
```toml
|
||||
[[language]]
|
||||
name = "mylang"
|
||||
scope = "source.mylang"
|
||||
injection-regex = "mylang"
|
||||
file-types = ["mylang", "myl"]
|
||||
comment-tokens = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
formatter = { command = "mylang-formatter" , args = ["--stdin"] }
|
||||
language-servers = [ "mylang-lsp" ]
|
||||
```
|
||||
# in <config_dir>/helix/languages.toml
|
||||
|
||||
These configuration keys are available:
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `name` | The name of the language |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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. |
|
||||
| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
|
||||
| `shebangs` | The interpreters from the shebang line, for example `["sh", "bash"]` |
|
||||
| `roots` | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
|
||||
| `auto-format` | Whether to autoformat this language when saving |
|
||||
| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `error`, `warning`, `info`, `hint`) |
|
||||
| `comment-tokens` | The tokens to use as a comment token, either a single token `"//"` or an array `["//", "///", "//!"]` (the first token will be used for commenting). Also configurable as `comment-token` for backwards compatibility|
|
||||
| `block-comment-tokens`| The start and end tokens for a multiline comment either an array or single table of `{ start = "/*", end = "*/"}`. The first set of tokens will be used for commenting, any pairs in the array can be uncommented |
|
||||
| `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) |
|
||||
| `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) |
|
||||
| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
|
||||
| `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. The filename of the current buffer can be passed as argument by using the `%{buffer_name}` expansion variable. See below for more information in the [Configuring the formatter command](#configuring-the-formatter-command) |
|
||||
| `soft-wrap` | [editor.softwrap](./editor.md#editorsoft-wrap-section)
|
||||
| `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` |
|
||||
| `rulers` | Overrides the `editor.rulers` config key for the language. |
|
||||
| `path-completion` | Overrides the `editor.path-completion` config key for the language. |
|
||||
| `word-completion` | Overrides the [`editor.word-completion`](./editor.md#editorword-completion-section) configuration for the language. |
|
||||
| `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. |
|
||||
| `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.
|
||||
| `rainbow-brackets` | Overrides the `editor.rainbow-brackets` config key for the language |
|
||||
|
||||
### File-type detection and the `file-types` key
|
||||
|
||||
Helix determines which language configuration to use based on the `file-types` key
|
||||
from the above section. `file-types` is a list of strings or tables, for
|
||||
example:
|
||||
|
||||
```toml
|
||||
file-types = ["toml", { glob = "Makefile" }, { glob = ".git/config" }, { glob = ".github/workflows/*.yaml" } ]
|
||||
```
|
||||
|
||||
When determining a language configuration to use, Helix searches the file-types
|
||||
with the following priorities:
|
||||
|
||||
1. Glob: values in `glob` tables are checked against the full path of the given
|
||||
file. Globs are standard Unix-style path globs (e.g. the kind you use in Shell)
|
||||
and can be used to match paths for a specific prefix, suffix, directory, etc.
|
||||
In the above example, the `{ glob = "Makefile" }` config would match files
|
||||
with the name `Makefile`, the `{ glob = ".git/config" }` config would match
|
||||
`config` files in `.git` directories, and the `{ glob = ".github/workflows/*.yaml" }`
|
||||
config would match any `yaml` files in `.github/workflow` directories. Note
|
||||
that globs should always use the Unix path separator `/` even on Windows systems;
|
||||
the matcher will automatically take the machine-specific separators into account.
|
||||
If the glob isn't an absolute path or doesn't already start with a glob prefix,
|
||||
`*/` will automatically be added to ensure it matches for any subdirectory.
|
||||
2. Extension: if there are no glob matches, any `file-types` string that matches
|
||||
the file extension of a given file wins. In the example above, the `"toml"`
|
||||
config matches files like `Cargo.toml` or `languages.toml`.
|
||||
|
||||
### Configuring the formatter command
|
||||
|
||||
[Command line expansions](./command-line.md#expansions) are supported in the arguments
|
||||
of the formatter command. In particular, the `%{buffer_name}` variable can be passed as
|
||||
argument to the formatter:
|
||||
|
||||
```toml
|
||||
formatter = { command = "mylang-formatter" , args = ["--stdin", "--stdin-filename", "%{buffer_name}"] }
|
||||
```
|
||||
|
||||
## Language Server configuration
|
||||
|
||||
Language servers are configured separately in the table `language-server` in the same file as the languages `languages.toml`
|
||||
|
||||
For example:
|
||||
|
||||
```toml
|
||||
[language-server.mylang-lsp]
|
||||
command = "mylang-lsp"
|
||||
args = ["--stdio"]
|
||||
config = { provideFormatter = true }
|
||||
environment = { "ENV1" = "value1", "ENV2" = "value2" }
|
||||
|
||||
[language-server.efm-lsp-prettier]
|
||||
command = "efm-langserver"
|
||||
|
||||
[language-server.efm-lsp-prettier.config]
|
||||
documentFormatting = true
|
||||
languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] }
|
||||
```
|
||||
|
||||
These are the available options for a language server.
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` |
|
||||
| `args` | A list of arguments to pass to the language server binary |
|
||||
| `config` | Language server initialization options |
|
||||
| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
|
||||
| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
|
||||
| `required-root-patterns` | A list of `glob` patterns to look for in the working directory. The language server is started if at least one of them is found. |
|
||||
|
||||
A `format` sub-table within `config` can be used to pass extra formatting options to
|
||||
[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
|
||||
For example, with typescript:
|
||||
|
||||
```toml
|
||||
[language-server.typescript-language-server]
|
||||
# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
|
||||
config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
|
||||
```
|
||||
|
||||
### Configuring Language Servers for a language
|
||||
|
||||
The `language-servers` attribute in a language tells helix which language servers are used for this language.
|
||||
|
||||
They have to be defined in the `[language-server]` table as described in the previous section.
|
||||
|
||||
Different languages can use the same language server instance, e.g. `typescript-language-server` is used for javascript, jsx, tsx and typescript by default.
|
||||
|
||||
The definition order of language servers affects the order in the results list of code action menu.
|
||||
|
||||
In case multiple language servers are specified in the `language-servers` attribute of a `language`,
|
||||
it's often useful to only enable/disable certain language-server features for these language servers.
|
||||
|
||||
As an example, `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
|
||||
so everything else should be handled by the `typescript-language-server` (which is configured by default).
|
||||
The language configuration for typescript could look like this:
|
||||
|
||||
```toml
|
||||
[[language]]
|
||||
name = "typescript"
|
||||
language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ]
|
||||
```
|
||||
|
||||
or equivalent:
|
||||
|
||||
```toml
|
||||
[[language]]
|
||||
name = "typescript"
|
||||
language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ]
|
||||
```
|
||||
|
||||
Each requested LSP feature is prioritized in the order of the `language-servers` array.
|
||||
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`).
|
||||
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.
|
||||
If no `except-features` or `only-features` is given, all features for the language server are enabled.
|
||||
If a language server itself doesn't support a feature, the next language server array entry will be tried (and so on).
|
||||
|
||||
The list of supported features is:
|
||||
|
||||
- `format`
|
||||
- `goto-definition`
|
||||
- `goto-declaration`
|
||||
- `goto-type-definition`
|
||||
- `goto-reference`
|
||||
- `goto-implementation`
|
||||
- `signature-help`
|
||||
- `hover`
|
||||
- `document-highlight`
|
||||
- `completion`
|
||||
- `code-action`
|
||||
- `workspace-command`
|
||||
- `document-symbols`
|
||||
- `workspace-symbols`
|
||||
- `diagnostics`
|
||||
- `rename-symbol`
|
||||
- `inlay-hints`
|
||||
|
||||
## Tree-sitter grammar configuration
|
||||
|
||||
The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
|
||||
section in `languages.toml`. For example:
|
||||
|
||||
```toml
|
||||
[[grammar]]
|
||||
name = "mylang"
|
||||
source = { git = "https://github.com/example/mylang", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }
|
||||
name = "rust"
|
||||
source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }
|
||||
|
||||
[[grammar]]
|
||||
name = "c"
|
||||
source = { path = "/path/to/tree-sitter-c" }
|
||||
```
|
||||
|
||||
Grammar configuration takes these keys:
|
||||
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `name` | The name of the tree-sitter grammar |
|
||||
| `source` | The method of fetching the grammar - a table with a schema defined below |
|
||||
|
||||
Where `source` is a table with either these keys when using a grammar from a
|
||||
git repository:
|
||||
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `git` | A git remote URL from which the grammar should be cloned |
|
||||
| `rev` | The revision (commit hash or tag) which should be fetched |
|
||||
| `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 |
|
||||
|
||||
### Choosing grammars
|
||||
|
||||
You may use a top-level `use-grammars` key to control which grammars are
|
||||
fetched and built when using `hx --grammar fetch` and `hx --grammar build`.
|
||||
You may use a top-level `use-grammars` key to control which grammars are fetched and built.
|
||||
|
||||
```toml
|
||||
# Note: this key must come **before** the [[language]] and [[grammar]] sections
|
||||
@@ -253,5 +38,3 @@ use-grammars = { except = [ "yaml", "json" ] }
|
||||
```
|
||||
|
||||
When omitted, all grammars are fetched and built.
|
||||
|
||||
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#language-injection
|
||||
|
@@ -1,35 +0,0 @@
|
||||
# Helix mode in other software
|
||||
|
||||
Helix' keymap and interaction model ([Using Helix](#usage.md)) is easier to adopt if it can be used consistently in many editing contexts. Yet, certain use cases cannot easily be addressed directly in Helix. Similar to vim, this leads to the creation of "Helix mode" in various other software products, allowing Helix-style editing for a greater variety of use cases.
|
||||
|
||||
"Helix mode" is frequently still in early stages or missing entirely. For such cases, we also link to relevant bugs or discussions.
|
||||
|
||||
## Other editors
|
||||
|
||||
| Editor | Plugin or feature providing Helix editing | Comments
|
||||
| --- | --- | --- |
|
||||
| [Vim](https://www.vim.org/) | [helix.vim](https://github.com/chtenb/helix.vim) config |
|
||||
| [IntelliJ IDEA](https://www.jetbrains.com/idea/) / [Android Studio](https://developer.android.com/studio)| [IdeaVim](https://plugins.jetbrains.com/plugin/164-ideavim) plugin + [helix.idea.vim](https://github.com/chtenb/helix.vim) config | Minimum recommended version is IdeaVim 2.19.0.
|
||||
| [Visual Studio](https://visualstudio.microsoft.com/) | [VsVim](https://marketplace.visualstudio.com/items?itemName=JaredParMSFT.VsVim) plugin + [helix.vs.vim](https://github.com/chtenb/helix.vim) config |
|
||||
| [Visual Studio Code](https://code.visualstudio.com/) | [Dance](https://marketplace.visualstudio.com/items?itemName=gregoire.dance) extension, or its [Helix fork](https://marketplace.visualstudio.com/items?itemName=kend.dancehelixkey) | The Helix fork has diverged. You can also use the original Dance and tweak its keybindings directly (try [this config](https://github.com/71/dance/issues/299#issuecomment-1655509531)).
|
||||
| [Visual Studio Code](https://code.visualstudio.com/) | [Helix for VS Code](https://marketplace.visualstudio.com/items?itemName=jasew.vscode-helix-emulation) extension|
|
||||
| [Zed](https://zed.dev/) | native via keybindings ([Bug](https://github.com/zed-industries/zed/issues/4642)) |
|
||||
| [CodeMirror](https://codemirror.net/) | [codemirror-helix](https://gitlab.com/_rvidal/codemirror-helix) |
|
||||
| [Lite XL](https://lite-xl.com/) | [lite-modal-hx](https://codeberg.org/Mandarancio/lite-modal-hx) |
|
||||
| [Lapce](https://lap.dev/lapce/) | | Requested: https://github.com/lapce/lapce/issues/281 |
|
||||
|
||||
|
||||
## Shells
|
||||
|
||||
| Shell | Plugin or feature providing Helix editing
|
||||
| --- | ---
|
||||
| Fish | [Feature Request](https://github.com/fish-shell/fish-shell/issues/7748)
|
||||
| Fish | [fish-helix](https://github.com/sshilovsky/fish-helix/tree/main)
|
||||
| Zsh | [helix-zsh](https://github.com/john-h-k/helix-zsh) or [zsh-helix-mode](https://github.com/Multirious/zsh-helix-mode)
|
||||
| Nushell | [Feature Request](https://github.com/nushell/reedline/issues/639)
|
||||
|
||||
## Other software
|
||||
|
||||
| Software | Plugin or feature providing Helix editing. | Comments
|
||||
| --- | --- | --- |
|
||||
| [Obsidian](https://obsidian.md/) | [Obsidian-Helix](https://github.com/Sinono3/obsidian-helix) | Uses `codemirror-helix` listed above.
|
@@ -1,155 +0,0 @@
|
||||
## Package managers
|
||||
|
||||
- [Linux](#linux)
|
||||
- [Ubuntu/Debian](#ubuntudebian)
|
||||
- [Fedora/RHEL](#fedorarhel)
|
||||
- [Arch Linux extra](#arch-linux-extra)
|
||||
- [NixOS](#nixos)
|
||||
- [Flatpak](#flatpak)
|
||||
- [Snap](#snap)
|
||||
- [AppImage](#appimage)
|
||||
- [macOS](#macos)
|
||||
- [Homebrew Core](#homebrew-core)
|
||||
- [MacPorts](#macports)
|
||||
- [Windows](#windows)
|
||||
- [Winget](#winget)
|
||||
- [Scoop](#scoop)
|
||||
- [Chocolatey](#chocolatey)
|
||||
- [MSYS2](#msys2)
|
||||
|
||||
[](https://repology.org/project/helix-editor/versions)
|
||||
|
||||
## Linux
|
||||
|
||||
The following third party repositories are available:
|
||||
|
||||
### Ubuntu/Debian
|
||||
|
||||
Install the Debian package [from the release page](https://github.com/helix-editor/helix/releases/latest).
|
||||
|
||||
If you are running a system older than Ubuntu 22.04, Mint 21, or Debian 12, you can build the `.deb` file locally
|
||||
[from source](./building-from-source.md#building-the-debian-package).
|
||||
|
||||
### Fedora/RHEL
|
||||
|
||||
```sh
|
||||
sudo dnf install helix
|
||||
```
|
||||
|
||||
### Arch Linux extra
|
||||
|
||||
Releases are available in the `extra` repository:
|
||||
|
||||
```sh
|
||||
sudo pacman -S helix
|
||||
```
|
||||
|
||||
> 💡 When installed from the `extra` repository, run Helix with `helix` instead of `hx`.
|
||||
>
|
||||
> For example:
|
||||
> ```sh
|
||||
> helix --health
|
||||
> ```
|
||||
> to check health
|
||||
|
||||
Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available
|
||||
in the AUR, which builds the master branch.
|
||||
|
||||
### NixOS
|
||||
|
||||
Helix is available in [nixpkgs](https://github.com/nixos/nixpkgs) through the `helix` attribute,
|
||||
the unstable channel usually carries the latest release.
|
||||
|
||||
Helix is also available as a [flake](https://wiki.nixos.org/wiki/Flakes) in the project
|
||||
root. Use `nix develop` to spin up a reproducible development shell. Outputs are
|
||||
cached for each push to master using [Cachix](https://www.cachix.org/). The
|
||||
flake is configured to automatically make use of this cache assuming the user
|
||||
accepts the new settings on first use.
|
||||
|
||||
If you are using a version of Nix without flakes enabled,
|
||||
[install Cachix CLI](https://docs.cachix.org/installation) and use
|
||||
`cachix use helix` to configure Nix to use cached outputs when possible.
|
||||
|
||||
### Flatpak
|
||||
|
||||
Helix is available on [Flathub](https://flathub.org/en-GB/apps/com.helix_editor.Helix):
|
||||
|
||||
```sh
|
||||
flatpak install flathub com.helix_editor.Helix
|
||||
flatpak run com.helix_editor.Helix
|
||||
```
|
||||
|
||||
### Snap
|
||||
|
||||
Helix is available on [Snapcraft](https://snapcraft.io/helix) and can be installed with:
|
||||
|
||||
```sh
|
||||
snap install --classic helix
|
||||
```
|
||||
|
||||
This will install Helix as both `/snap/bin/helix` and `/snap/bin/hx`, so make sure `/snap/bin` is in your `PATH`.
|
||||
|
||||
### AppImage
|
||||
|
||||
Install Helix using the Linux [AppImage](https://appimage.org/) format.
|
||||
Download the official Helix AppImage from the [latest releases](https://github.com/helix-editor/helix/releases/latest) page.
|
||||
|
||||
```sh
|
||||
chmod +x helix-*.AppImage # change permission for executable mode
|
||||
./helix-*.AppImage # run helix
|
||||
```
|
||||
|
||||
You can optionally [add the `.desktop` file](./building-from-source.md#configure-the-desktop-shortcut). Helix must be installed in `PATH` with the name `hx`. For example:
|
||||
```sh
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
mv helix-*.AppImage "$HOME/.local/bin/hx"
|
||||
```
|
||||
|
||||
and make sure `~/.local/bin` is in your `PATH`.
|
||||
|
||||
## macOS
|
||||
|
||||
### Homebrew Core
|
||||
|
||||
```sh
|
||||
brew install helix
|
||||
```
|
||||
|
||||
### MacPorts
|
||||
|
||||
```sh
|
||||
sudo port install helix
|
||||
```
|
||||
|
||||
## Windows
|
||||
|
||||
Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
|
||||
or [MSYS2](https://msys2.org/).
|
||||
|
||||
### Winget
|
||||
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.
|
||||
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.
|
||||
|
||||
```sh
|
||||
winget install Helix.Helix
|
||||
```
|
||||
|
||||
### Scoop
|
||||
|
||||
```sh
|
||||
scoop install helix
|
||||
```
|
||||
|
||||
### Chocolatey
|
||||
|
||||
```sh
|
||||
choco install helix
|
||||
```
|
||||
|
||||
### MSYS2
|
||||
|
||||
For 64-bit Windows 8.1 or above:
|
||||
|
||||
```sh
|
||||
pacman -S mingw-w64-ucrt-x86_64-helix
|
||||
```
|
@@ -1,11 +0,0 @@
|
||||
## Using pickers
|
||||
|
||||
Helix has a variety of pickers, which are interactive windows used to select various kinds of items. These include a file picker, global search picker, and more. Most pickers are accessed via keybindings in [space mode](./keymap.md#space-mode). Pickers have their own [keymap](./keymap.md#picker) for navigation.
|
||||
|
||||
### Filtering Picker Results
|
||||
|
||||
Most pickers perform fuzzy matching using [fzf syntax](https://github.com/junegunn/fzf?tab=readme-ov-file#search-syntax). Two exceptions are the global search picker, which uses regex, and the workspace symbol picker, which passes search terms to the language server. Note that OR operations (`|`) are not currently supported.
|
||||
|
||||
If a picker shows multiple columns, you may apply the filter to a specific column by prefixing the column name with `%`. Column names can be shortened to any prefix, so `%p`, `%pa` or `%pat` all mean the same as `%path`. For example, a query of `helix %p .toml !lang` in the global search picker searches for the term "helix" within files with paths ending in ".toml" but not including "lang".
|
||||
|
||||
You can insert the contents of a [register](./registers.md) using `Ctrl-r` followed by a register name. For example, one could insert the currently selected text using `Ctrl-r`-`.`, or the directory of the current file using `Ctrl-r`-`%` followed by `Ctrl-w` to remove the last path section. The global search picker will use the contents of the [search register](./registers.md#default-registers) if you press `Enter` without typing a filter. For example, pressing `*`-`Space-/`-`Enter` will start a global search for the currently selected text.
|
@@ -1,54 +0,0 @@
|
||||
## Registers
|
||||
|
||||
- [User-defined registers](#user-defined-registers)
|
||||
- [Default registers](#default-registers)
|
||||
- [Special registers](#special-registers)
|
||||
|
||||
In Helix, registers are storage locations for text and other data, such as the
|
||||
result of a search. Registers can be used to cut, copy, and paste text, similar
|
||||
to the clipboard in other text editors. Usage is similar to Vim, with `"` being
|
||||
used to select a register.
|
||||
|
||||
### User-defined registers
|
||||
|
||||
Helix allows you to create your own named registers for storing text, for
|
||||
example:
|
||||
|
||||
- `"ay` - Yank the current selection to register `a`.
|
||||
- `"op` - Paste the text in register `o` after the selection.
|
||||
|
||||
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:
|
||||
|
||||
- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
|
||||
- `"md` - Store the selection in register `m` and delete it.
|
||||
|
||||
### Default registers
|
||||
|
||||
Commands that use registers, like yank (`y`), use a default register if none is specified.
|
||||
These registers are used as defaults:
|
||||
|
||||
| Register character | Contains |
|
||||
| --- | --- |
|
||||
| `/` | Last search |
|
||||
| `:` | Last executed command |
|
||||
| `"` | Last yanked text |
|
||||
| `@` | Last recorded macro |
|
||||
|
||||
### Special registers
|
||||
|
||||
Some registers have special behavior when read from and written to.
|
||||
|
||||
| Register character | When read | When written |
|
||||
| --- | --- | --- |
|
||||
| `_` | No values are returned | All values are discarded |
|
||||
| `#` | Selection indices (first selection is `1`, second is `2`, etc.) | This register is not writable |
|
||||
| `.` | Contents of the current selections | This register is not writable |
|
||||
| `%` | Name of the current file | This register is not writable |
|
||||
| `+` | Reads from the system clipboard | Joins and yanks to the system clipboard |
|
||||
| `*` | Reads from the primary clipboard | Joins and yanks to the primary clipboard |
|
||||
|
||||
When yanking multiple selections to the clipboard registers, the selections
|
||||
are joined with newlines. Pasting from these registers will paste multiple
|
||||
selections if the clipboard was last yanked to by the Helix session. Otherwise
|
||||
the clipboard contents are pasted as one selection.
|
||||
|
@@ -1,99 +1,44 @@
|
||||
## Key remapping
|
||||
# Key Remapping
|
||||
|
||||
Helix currently supports one-way key remapping through a simple TOML configuration
|
||||
One-way key remapping is temporarily supported via a simple TOML configuration
|
||||
file. (More powerful solutions such as rebinding via commands will be
|
||||
available in the future).
|
||||
|
||||
There are three kinds of commands that can be used in keymaps:
|
||||
|
||||
* Static commands: commands like `move_char_right` which are usually bound to
|
||||
keys and used for movement and editing. A list of static commands is
|
||||
available in the [Keymap](./keymap.html) documentation and in the source code
|
||||
in [`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.
|
||||
* Typable commands: commands that can be executed from command mode (`:`), for
|
||||
example `:write!`. See the [Commands](./commands.md) documentation for a
|
||||
list of available typeable commands or the `TypableCommandList` declaration in
|
||||
the source code at [`helix-term/src/commands/typed.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands/typed.rs).
|
||||
* Macros: sequences of keys that are executed in order. These keybindings
|
||||
start with `@` and then list any number of keys to be executed. For example
|
||||
`@miw` can be used to select the surrounding word. For now, macro keybindings
|
||||
are not allowed in keybinding sequences due to limitations in the way that
|
||||
command sequences are executed. Modifier keys (e.g. Alt+o) can be used
|
||||
like `"<A-o>"`, e.g. `"@miw<A-o>"`
|
||||
|
||||
To remap keys, create a `config.toml` file in your `helix` configuration
|
||||
directory (default `~/.config/helix` on Linux systems) with a structure like
|
||||
To remap keys, write a `config.toml` file in your `helix` configuration
|
||||
directory (default `~/.config/helix` in Linux systems) with a structure like
|
||||
this:
|
||||
|
||||
> 💡 To set a modifier + key as a keymap, type `A-X = ...` or `C-X = ...` for Alt + X or Ctrl + X. Combine with Shift using a dash, e.g. `C-S-esc`.
|
||||
> Within macros, wrap them in `<>`, e.g. `<A-X>` and `<C-X>` to distinguish from the `A` or `C` keys.
|
||||
|
||||
```toml
|
||||
# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
|
||||
[keys.normal]
|
||||
C-s = ":w" # Maps Ctrl-s to the typable command :w which is an alias for :write (save file)
|
||||
C-o = ":open ~/.config/helix/config.toml" # Maps Ctrl-o to opening of the helix config file
|
||||
C-s = ":w" # Maps the Control-s to the typable command :w which is an alias for :write (save file)
|
||||
C-o = ":open ~/.config/helix/config.toml" # Maps the Control-o to opening of the helix config file
|
||||
a = "move_char_left" # Maps the 'a' key to the move_char_left command
|
||||
w = "move_line_up" # Maps the 'w' key move_line_up
|
||||
"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
|
||||
"C-S-esc" = "extend_line" # Maps Control-Shift-Escape to extend_line
|
||||
g = { a = "code_action" } # Maps `ga` to show possible code actions
|
||||
"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
|
||||
"A-x" = "@x<A-d>" # Maps Alt-x to a macro selecting the whole line and deleting it without yanking it
|
||||
|
||||
[keys.insert]
|
||||
"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
|
||||
j = { k = "normal_mode" } # Maps `jk` to exit insert mode
|
||||
```
|
||||
> NOTE: Typable commands can also be remapped, remember to keep the `:` prefix to indicate it's a typable command.
|
||||
|
||||
## Minor modes
|
||||
|
||||
Minor modes are accessed by pressing a key (usually from normal mode), giving access to dedicated bindings. Bindings
|
||||
can be modified or added by nesting definitions.
|
||||
|
||||
```toml
|
||||
[keys.insert.j]
|
||||
k = "normal_mode" # Maps `jk` to exit insert mode
|
||||
|
||||
[keys.normal.g]
|
||||
a = "code_action" # Maps `ga` to show possible code actions
|
||||
|
||||
# invert `j` and `k` in view mode
|
||||
[keys.normal.z]
|
||||
j = "scroll_up"
|
||||
k = "scroll_down"
|
||||
|
||||
# create a new minor mode bound to `+`
|
||||
[keys.normal."+"]
|
||||
m = ":run-shell-command make"
|
||||
c = ":run-shell-command cargo build"
|
||||
t = ":run-shell-command cargo test"
|
||||
```
|
||||
|
||||
## Special keys and modifiers
|
||||
|
||||
Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes `C-`, `S-` and `A-`.
|
||||
|
||||
The [Super key](https://en.wikipedia.org/wiki/Super_key_(keyboard_button)) - the Windows/Linux
|
||||
key or the Command key on Mac keyboards - is also supported when using a terminal emulator that
|
||||
supports the [enhanced keyboard protocol](https://github.com/helix-editor/helix/wiki/Terminal-Support#enhanced-keyboard-protocol).
|
||||
The super key is encoded with prefixes `Meta-`, `Cmd-` or `Win-`. These are all synonyms for the
|
||||
super modifier - binding a key with a `Win-` modifier will mean it can be used with the
|
||||
Windows/Linux key or the Command key.
|
||||
|
||||
```toml
|
||||
[keys.normal]
|
||||
C-s = ":write" # Ctrl and 's' to write
|
||||
Cmd-s = ":write" # Cmd or Win or Meta and 's' to write
|
||||
```
|
||||
|
||||
Special keys are encoded as follows:
|
||||
Control, Shift and Alt modifiers are encoded respectively with the prefixes
|
||||
`C-`, `S-` and `A-`. Special keys are encoded as follows:
|
||||
|
||||
| Key name | Representation |
|
||||
| --- | --- |
|
||||
| Backspace | `"backspace"` |
|
||||
| Space | `"space"` |
|
||||
| Return/Enter | `"ret"` |
|
||||
| < | `"lt"` |
|
||||
| \> | `"gt"` |
|
||||
| \+ | `"plus"` |
|
||||
| \- | `"minus"` |
|
||||
| ; | `"semicolon"` |
|
||||
| % | `"percent"` |
|
||||
| Left | `"left"` |
|
||||
| Right | `"right"` |
|
||||
| Up | `"up"` |
|
||||
@@ -107,18 +52,8 @@ Special keys are encoded as follows:
|
||||
| Insert | `"ins"` |
|
||||
| Null | `"null"` |
|
||||
| Escape | `"esc"` |
|
||||
| Less Than (<) | `"lt"` |
|
||||
| Greater Than (>) | `"gt"` |
|
||||
|
||||
Keys can be disabled by binding them to the `no_op` command.
|
||||
|
||||
All other keys such as `?`, `!`, `-` etc. can be used literally:
|
||||
|
||||
```toml
|
||||
[keys.normal]
|
||||
"?" = ":write"
|
||||
"!" = ":write"
|
||||
"-" = ":write"
|
||||
```
|
||||
|
||||
Note: `-` can't be used when combined with a modifier, for example `Alt` + `-` should be written as `A-minus`. `A--` is not accepted.
|
||||
Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands.
|
||||
> Commands can also be found 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`.
|
||||
|
@@ -1,24 +0,0 @@
|
||||
## Surround
|
||||
|
||||
Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround).
|
||||
The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
|
||||
|
||||

|
||||
|
||||
| Key Sequence | Action |
|
||||
| --------------------------------- | --------------------------------------- |
|
||||
| `ms<char>` (after selecting text) | Add surround characters to selection |
|
||||
| `mr<char_to_replace><new_char>` | Replace the closest surround characters |
|
||||
| `md<char_to_delete>` | Delete the closest surround characters |
|
||||
|
||||
You can use counts to act on outer pairs.
|
||||
|
||||
Surround can also act on multiple selections. For example, to change every occurrence of `(use)` to `[use]`:
|
||||
|
||||
1. `%` to select the whole file
|
||||
2. `s` to split the selections on a search term
|
||||
3. Input `use` and hit Enter
|
||||
4. `mr([` to replace the parentheses with square brackets
|
||||
|
||||
Multiple characters are currently not supported, but planned for future release.
|
||||
|
@@ -1,66 +0,0 @@
|
||||
## Moving the selection with syntax-aware motions
|
||||
|
||||
`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to move the
|
||||
selection according to its location in the syntax tree. For example, many languages have the
|
||||
following syntax for function calls:
|
||||
|
||||
```js
|
||||
func(arg1, arg2, arg3);
|
||||
```
|
||||
|
||||
A function call might be parsed by tree-sitter into a tree like the following.
|
||||
|
||||
```tsq
|
||||
(call
|
||||
function: (identifier) ; func
|
||||
arguments:
|
||||
(arguments ; (arg1, arg2, arg3)
|
||||
(identifier) ; arg1
|
||||
(identifier) ; arg2
|
||||
(identifier))) ; arg3
|
||||
```
|
||||
|
||||
Use `:tree-sitter-subtree` to view the syntax tree of the primary selection. In
|
||||
a more intuitive tree format:
|
||||
|
||||
```
|
||||
┌────┐
|
||||
│call│
|
||||
┌─────┴────┴─────┐
|
||||
│ │
|
||||
┌─────▼────┐ ┌────▼────┐
|
||||
│identifier│ │arguments│
|
||||
│ "func" │ ┌────┴───┬─────┴───┐
|
||||
└──────────┘ │ │ │
|
||||
│ │ │
|
||||
┌─────────▼┐ ┌────▼─────┐ ┌▼─────────┐
|
||||
│identifier│ │identifier│ │identifier│
|
||||
│ "arg1" │ │ "arg2" │ │ "arg3" │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
If you have a selection that wraps `arg1` (see the tree above), and you use
|
||||
`Alt-n`, it will select the next sibling in the syntax tree: `arg2`.
|
||||
|
||||
```js
|
||||
// before
|
||||
func([arg1], arg2, arg3)
|
||||
// after
|
||||
func(arg1, [arg2], arg3);
|
||||
```
|
||||
|
||||
Similarly, `Alt-o` will expand the selection to the parent node, in this case, the
|
||||
arguments node.
|
||||
|
||||
```js
|
||||
func[(arg1, arg2, arg3)];
|
||||
```
|
||||
|
||||
There is also some nuanced behavior that prevents you from getting stuck on a
|
||||
node with no sibling. When using `Alt-p` with a selection on `arg1`, the previous
|
||||
child node will be selected. In the event that `arg1` does not have a previous
|
||||
sibling, the selection will move up the syntax tree and select the previous
|
||||
element. As a result, using `Alt-p` with a selection on `arg1` will move the
|
||||
selection to the "func" `identifier`.
|
||||
|
||||
[lang-support]: ./lang-support.md
|
@@ -1,48 +0,0 @@
|
||||
## Selecting and manipulating text with textobjects
|
||||
|
||||
In Helix, textobjects are a way to select, manipulate and operate on a piece of
|
||||
text in a structured way. They allow you to refer to blocks of text based on
|
||||
their structure or purpose, such as a word, sentence, paragraph, or even a
|
||||
function or block of code.
|
||||
|
||||

|
||||

|
||||
|
||||
- `ma` - Select around the object (`va` in Vim, `<alt-a>` in Kakoune)
|
||||
- `mi` - Select inside the object (`vi` in Vim, `<alt-i>` in Kakoune)
|
||||
|
||||
| Key after `mi` or `ma` | Textobject selected |
|
||||
| --- | --- |
|
||||
| `w` | Word |
|
||||
| `W` | WORD |
|
||||
| `p` | Paragraph |
|
||||
| `(`, `[`, `'`, etc. | Specified surround pairs |
|
||||
| `m` | The closest surround pair |
|
||||
| `f` | Function |
|
||||
| `t` | Type (or Class) |
|
||||
| `a` | Argument/parameter |
|
||||
| `c` | Comment |
|
||||
| `T` | Test |
|
||||
| `g` | Change |
|
||||
| `x` | (X)HTML element |
|
||||
|
||||
> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
|
||||
document and a special tree-sitter query file to work properly. [Only
|
||||
some grammars](./lang-support.md) currently have the query file implemented.
|
||||
Contributions are welcome!
|
||||
|
||||
## Navigating using tree-sitter textobjects
|
||||
|
||||
Navigating between functions, classes, parameters, and other elements is
|
||||
possible using tree-sitter and textobject queries. For
|
||||
example to move to the next function use `]f`, to move to previous
|
||||
type use `[t`, and so on.
|
||||
|
||||

|
||||
|
||||
For the full reference see the [unimpaired](./keymap.html#unimpaired) section of the key bind
|
||||
documentation.
|
||||
|
||||
> 💡 This feature relies on tree-sitter textobjects
|
||||
> and requires the corresponding query file to work properly.
|
||||
|
@@ -1,34 +1,22 @@
|
||||
## Themes
|
||||
# Themes
|
||||
|
||||
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>`.
|
||||
|
||||
Separate themes can be configured for light and dark modes. On terminals supporting [mode 2031 dark/light detection](https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md), the theme mode is detected from the terminal.
|
||||
|
||||
```toml
|
||||
[theme]
|
||||
dark = "catppuccin_frappe"
|
||||
light = "catppuccin_latte"
|
||||
## Optional. Used if the terminal doesn't declare a preference.
|
||||
## Defaults to the theme set for `dark` if not specified.
|
||||
# fallback = "catppuccin_frappe"
|
||||
```
|
||||
To use a theme add `theme = "<name>"` to your [`config.toml`](./configuration.md) at the very top of the file before the first section or select it during runtime using `:theme <name>`.
|
||||
|
||||
## Creating a theme
|
||||
|
||||
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.
|
||||
Create a file with the name of your theme as file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes`). The directory might have to be created beforehand.
|
||||
|
||||
> 💡 The names "default" and "base16_default" are reserved for built-in themes
|
||||
> and cannot be overridden by user-defined themes.
|
||||
The names "default" and "base16_default" are reserved for the builtin themes and cannot be overridden by user defined themes.
|
||||
|
||||
### Overview
|
||||
The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
|
||||
|
||||
Each line in the theme file is specified as below:
|
||||
|
||||
```toml
|
||||
key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }
|
||||
key = { fg = "#ffffff", bg = "#000000", modifiers = ["bold", "italic"] }
|
||||
```
|
||||
|
||||
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.
|
||||
where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, and `modifiers` is a list of style modifiers. `bg` and `modifiers` can be omitted to defer to the defaults.
|
||||
|
||||
To specify only the foreground color:
|
||||
|
||||
@@ -36,37 +24,29 @@ To specify only the foreground color:
|
||||
key = "#ffffff"
|
||||
```
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
```toml
|
||||
"key.key" = "#ffffff"
|
||||
```
|
||||
|
||||
For inspiration, you can find the default `theme.toml`
|
||||
[here](https://github.com/helix-editor/helix/blob/master/theme.toml) and
|
||||
user-submitted themes
|
||||
[here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
|
||||
|
||||
|
||||
## The details of theme creation
|
||||
|
||||
### Color palettes
|
||||
|
||||
It's recommended to define a palette of named colors, and refer to them in the
|
||||
It's recommended define a palette of named colors, and refer to them from the
|
||||
configuration values in your theme. To do this, add a table called
|
||||
`palette` to your theme file:
|
||||
|
||||
```toml
|
||||
"ui.background" = "white"
|
||||
"ui.text" = "black"
|
||||
ui.background = "white"
|
||||
ui.text = "black"
|
||||
|
||||
[palette]
|
||||
white = "#ffffff"
|
||||
black = "#000000"
|
||||
```
|
||||
|
||||
Keep in mind that the `[palette]` table includes all keys after its header,
|
||||
so it should be defined after the normal theme options.
|
||||
Remember that the `[palette]` table includes all keys after its header,
|
||||
so you should define the palette after normal theme options.
|
||||
|
||||
The default palette uses the terminal's default 16 colors, and the colors names
|
||||
are listed below. The `[palette]` section in the config file takes precedence
|
||||
@@ -74,7 +54,6 @@ over it and is merged into the default palette.
|
||||
|
||||
| Color Name |
|
||||
| --- |
|
||||
| `default` |
|
||||
| `black` |
|
||||
| `red` |
|
||||
| `green` |
|
||||
@@ -94,8 +73,9 @@ over it and is merged into the default palette.
|
||||
|
||||
### Modifiers
|
||||
|
||||
The following values may be used as modifier, provided they are supported by
|
||||
your terminal emulator.
|
||||
The following values may be used as modifiers.
|
||||
|
||||
Less common modifiers might not be supported by your terminal emulator.
|
||||
|
||||
| Modifier |
|
||||
| --- |
|
||||
@@ -109,73 +89,25 @@ your terminal emulator.
|
||||
| `hidden` |
|
||||
| `crossed_out` |
|
||||
|
||||
> 💡 The `underlined` modifier is deprecated and only available for backwards compatibility.
|
||||
> Its behavior is equivalent to setting `underline.style="line"`.
|
||||
|
||||
### Underline style
|
||||
|
||||
One of the following values may be used as a value for `underline.style`, providing it is
|
||||
supported by your terminal emulator.
|
||||
|
||||
| Modifier |
|
||||
| --- |
|
||||
| `line` |
|
||||
| `curl` |
|
||||
| `dashed` |
|
||||
| `dotted` |
|
||||
| `double_line` |
|
||||
|
||||
|
||||
### Inheritance
|
||||
|
||||
Extend other themes by setting the `inherits` property to an existing theme.
|
||||
|
||||
```toml
|
||||
inherits = "boo_berry"
|
||||
|
||||
# Override the theming for "keyword"s:
|
||||
"keyword" = { fg = "gold" }
|
||||
|
||||
# Override colors in the palette:
|
||||
[palette]
|
||||
berry = "#2A2A4D"
|
||||
```
|
||||
|
||||
### Rainbow
|
||||
|
||||
The `rainbow` key is used for rainbow highlight for matching brackets.
|
||||
The key is a list of styles.
|
||||
|
||||
```toml
|
||||
rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
|
||||
```
|
||||
|
||||
Colors from the palette and modifiers may be used.
|
||||
|
||||
### Scopes
|
||||
|
||||
The following is a list of scopes available to use for styling:
|
||||
The following is a list of scopes available to use for styling.
|
||||
|
||||
#### Syntax highlighting
|
||||
|
||||
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#highlights).
|
||||
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
|
||||
|
||||
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`.
|
||||
For a given highlight produced, styling will be determined based on the longest matching theme key. For example, the highlight `function.builtin.static` would match the key `function.builtin` rather than `function`.
|
||||
|
||||
We use a similar set of scopes as
|
||||
[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
|
||||
[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
|
||||
|
||||
- `attribute` - Class attributes, HTML tag attributes
|
||||
|
||||
- `type` - Types
|
||||
- `builtin` - Primitive types provided by the language (`int`, `usize`)
|
||||
- `parameter` - Generic type parameters (`T`)
|
||||
- `enum`
|
||||
- `variant`
|
||||
- `constructor`
|
||||
|
||||
- `constant` (TODO: constant.other.placeholder for `%v`)
|
||||
- `constant` (TODO: constant.other.placeholder for %v)
|
||||
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
|
||||
- `boolean`
|
||||
- `character`
|
||||
@@ -193,24 +125,21 @@ We use a similar set of scopes as
|
||||
|
||||
- `comment` - Code comments
|
||||
- `line` - Single line comments (`//`)
|
||||
- `documentation` - Line documentation comments (e.g. `///` in Rust)
|
||||
- `block` - Block comments (e.g. (`/* */`)
|
||||
- `documentation` - Block documentation comments (e.g. `/** */` in Rust)
|
||||
- `unused` - Unused variables and patterns, e.g. `_` and `_foo`
|
||||
- `documentation` - Documentation comments (e.g. `///` in Rust)
|
||||
|
||||
- `variable` - Variables
|
||||
- `builtin` - Reserved language variables (`self`, `this`, `super`, etc.)
|
||||
- `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
|
||||
- `parameter` - Function parameters
|
||||
- `other`
|
||||
- `member` - Fields of composite data types (e.g. structs, unions)
|
||||
- `private` - Private fields that use a unique syntax (currently just ECMAScript-based languages)
|
||||
- `function` (TODO: ?)
|
||||
|
||||
- `label` - `.class`, `#id` in CSS, etc.
|
||||
- `label`
|
||||
|
||||
- `punctuation`
|
||||
- `delimiter` - Commas, colons
|
||||
- `bracket` - Parentheses, angle brackets, etc.
|
||||
- `special` - String interpolation brackets.
|
||||
|
||||
- `keyword`
|
||||
- `control`
|
||||
@@ -222,26 +151,19 @@ We use a similar set of scopes as
|
||||
- `operator` - `or`, `in`
|
||||
- `directive` - Preprocessor directives (`#if` in C)
|
||||
- `function` - `fn`, `func`
|
||||
- `storage` - Keywords describing how things are stored
|
||||
- `type` - The type of something, `class`, `function`, `var`, `let`, etc.
|
||||
- `modifier` - Storage modifiers like `static`, `mut`, `const`, `ref`, etc.
|
||||
|
||||
- `operator` - `||`, `+=`, `>`
|
||||
|
||||
- `function`
|
||||
- `builtin`
|
||||
- `method`
|
||||
- `private` - Private methods that use a unique syntax (currently just ECMAScript-based languages)
|
||||
- `macro`
|
||||
- `special` (preprocessor in C)
|
||||
- `special` (preprocesor in C)
|
||||
|
||||
- `tag` - Tags (e.g. `<body>` in HTML)
|
||||
- `builtin`
|
||||
|
||||
- `namespace`
|
||||
|
||||
- `special` - `derive` in Rust, etc.
|
||||
|
||||
- `markup`
|
||||
- `heading`
|
||||
- `marker`
|
||||
@@ -249,15 +171,12 @@ We use a similar set of scopes as
|
||||
- `list`
|
||||
- `unnumbered`
|
||||
- `numbered`
|
||||
- `checked`
|
||||
- `unchecked`
|
||||
- `bold`
|
||||
- `italic`
|
||||
- `strikethrough`
|
||||
- `link`
|
||||
- `url` - URLs pointed to by links
|
||||
- `label` - non-URL link references
|
||||
- `text` - URL and image descriptions in links
|
||||
- `url` - urls pointed to by links
|
||||
- `label` - non-url link references
|
||||
- `text` - url and image descriptions in links
|
||||
- `quote`
|
||||
- `raw`
|
||||
- `inline`
|
||||
@@ -265,101 +184,53 @@ We use a similar set of scopes as
|
||||
|
||||
- `diff` - version control changes
|
||||
- `plus` - additions
|
||||
- `gutter` - gutter indicator
|
||||
- `minus` - deletions
|
||||
- `gutter` - gutter indicator
|
||||
- `delta` - modifications
|
||||
- `moved` - renamed or moved files/changes
|
||||
- `conflict` - merge conflicts
|
||||
- `gutter` - gutter indicator
|
||||
|
||||
#### Interface
|
||||
|
||||
These scopes are used for theming the editor interface:
|
||||
These scopes are used for theming the editor interface.
|
||||
|
||||
- `markup`
|
||||
- `normal`
|
||||
- `completion` - for completion doc popup UI
|
||||
- `hover` - for hover popup UI
|
||||
- `completion` - for completion doc popup ui
|
||||
- `hover` - for hover popup ui
|
||||
- `heading`
|
||||
- `completion` - for completion doc popup UI
|
||||
- `hover` - for hover popup UI
|
||||
- `completion` - for completion doc popup ui
|
||||
- `hover` - for hover popup ui
|
||||
- `raw`
|
||||
- `inline`
|
||||
- `completion` - for completion doc popup UI
|
||||
- `hover` - for hover popup UI
|
||||
- `completion` - for completion doc popup ui
|
||||
- `hover` - for hover popup ui
|
||||
|
||||
|
||||
| Key | Notes |
|
||||
| --- | --- |
|
||||
| `ui.background` | |
|
||||
| `ui.background.separator` | Picker separator below input line |
|
||||
| `ui.cursor` | |
|
||||
| `ui.cursor.normal` | |
|
||||
| `ui.cursor.insert` | |
|
||||
| `ui.cursor.select` | |
|
||||
| `ui.cursor.match` | Matching bracket etc. |
|
||||
| `ui.cursor.primary` | Cursor with primary selection |
|
||||
| `ui.cursor.primary.normal` | |
|
||||
| `ui.cursor.primary.insert` | |
|
||||
| `ui.cursor.primary.select` | |
|
||||
| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter |
|
||||
| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter |
|
||||
| `ui.gutter` | Gutter |
|
||||
| `ui.gutter.selected` | Gutter for the line the cursor is on |
|
||||
| `ui.linenr` | Line numbers |
|
||||
| `ui.linenr.selected` | Line number for the line the cursor is on |
|
||||
| `ui.linenr` | |
|
||||
| `ui.linenr.selected` | |
|
||||
| `ui.statusline` | Statusline |
|
||||
| `ui.statusline.inactive` | Statusline (unfocused document) |
|
||||
| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.separator` | Separator character in statusline |
|
||||
| `ui.bufferline` | Style for the buffer line |
|
||||
| `ui.bufferline.active` | Style for the active buffer in buffer line |
|
||||
| `ui.bufferline.background` | Style for bufferline background |
|
||||
| `ui.popup` | Documentation popups (e.g. Space + k) |
|
||||
| `ui.popup.info` | Prompt for multiple key options |
|
||||
| `ui.picker.header` | Header row area in pickers with multiple columns |
|
||||
| `ui.picker.header.column` | Column names in pickers with multiple columns |
|
||||
| `ui.picker.header.column.active` | The column name in pickers with multiple columns where the cursor is entering into. |
|
||||
| `ui.window` | Borderlines separating splits |
|
||||
| `ui.help` | Description box for commands |
|
||||
| `ui.text` | Default text style, command prompts, popup text, etc. |
|
||||
| `ui.text.focus` | The currently selected line in the picker |
|
||||
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
|
||||
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
|
||||
| `ui.text.directory` | Directory names in prompt completion |
|
||||
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
|
||||
| `ui.virtual.whitespace` | Visible whitespace characters |
|
||||
| `ui.virtual.indent-guide` | Vertical indent width guides |
|
||||
| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
|
||||
| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (language servers are not required to set a kind) |
|
||||
| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (language servers are not required to set a kind) |
|
||||
| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
|
||||
| `ui.virtual.jump-label` | Style for virtual jump labels |
|
||||
| `ui.menu` | Code and command completion menus |
|
||||
| `ui.menu.selected` | Selected autocomplete item |
|
||||
| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
|
||||
| `ui.popup` | |
|
||||
| `ui.popup.info` | |
|
||||
| `ui.window` | |
|
||||
| `ui.help` | |
|
||||
| `ui.text` | |
|
||||
| `ui.text.focus` | |
|
||||
| `ui.text.info` | |
|
||||
| `ui.menu` | |
|
||||
| `ui.menu.selected` | |
|
||||
| `ui.selection` | For selections in the editing area |
|
||||
| `ui.selection.primary` | |
|
||||
| `ui.highlight` | Highlighted lines in the picker preview |
|
||||
| `ui.highlight.frameline` | Line at which debugging execution is paused at |
|
||||
| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
|
||||
| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
|
||||
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
|
||||
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
|
||||
| `warning` | Diagnostics warning (gutter) |
|
||||
| `error` | Diagnostics error (gutter) |
|
||||
| `info` | Diagnostics info (gutter) |
|
||||
| `hint` | Diagnostics hint (gutter) |
|
||||
| `diagnostic` | Diagnostics fallback style (editing area) |
|
||||
| `diagnostic.hint` | Diagnostics hint (editing area) |
|
||||
| `diagnostic.info` | Diagnostics info (editing area) |
|
||||
| `diagnostic.warning` | Diagnostics warning (editing area) |
|
||||
| `diagnostic.error` | Diagnostics error (editing area) |
|
||||
| `diagnostic.unnecessary` | Diagnostics with unnecessary tag (editing area) |
|
||||
| `diagnostic.deprecated` | Diagnostics with deprecated tag (editing area) |
|
||||
| `tabstop` | Snippet placeholder |
|
||||
| `diagnostic` | For text in editing area |
|
||||
|
||||
[editor-section]: ./configuration.md#editor-section
|
||||
|
@@ -6,7 +6,6 @@ Docs for bleeding edge master can be found at
|
||||
See the [usage] section for a quick overview of the editor, [keymap]
|
||||
section for all available keybindings and the [configuration] section
|
||||
for defining custom keybindings, setting themes, etc.
|
||||
For everything else (e.g., how to install supported language servers), see the [Helix Wiki].
|
||||
|
||||
Refer the [FAQ] for common questions.
|
||||
|
||||
@@ -14,4 +13,3 @@ Refer the [FAQ] for common questions.
|
||||
[usage]: ./usage.md
|
||||
[keymap]: ./keymap.md
|
||||
[configuration]: ./configuration.md
|
||||
[Helix Wiki]: https://github.com/helix-editor/helix/wiki
|
@@ -1,33 +1,96 @@
|
||||
# Using Helix
|
||||
# Usage
|
||||
|
||||
For a full interactive introduction to Helix, refer to the
|
||||
[tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) which
|
||||
can be accessed via the command `hx --tutor` or `:tutor`.
|
||||
(Currently not fully documented, see the [keymappings](./keymap.md) list for more.)
|
||||
|
||||
> 💡 Currently, not all functionality is fully documented, please refer to the
|
||||
> [key mappings](./keymap.md) list.
|
||||
See [tutor.txt](https://github.com/helix-editor/helix/blob/master/runtime/tutor.txt) (accessible via `hx --tutor` or `:tutor`) for a vimtutor-like introduction.
|
||||
|
||||
## Modes
|
||||
## Registers
|
||||
|
||||
Helix is a modal editor, meaning it has different modes for different tasks. The main modes are:
|
||||
Vim-like registers can be used to yank and store text to be pasted later. Usage is similar, with `"` being used to select a register:
|
||||
|
||||
* [Normal mode](./keymap.md#normal-mode): For navigation and editing commands. This is the default mode.
|
||||
* [Insert mode](./keymap.md#insert-mode): For typing text directly into the document. Access by typing `i` in normal mode.
|
||||
* [Select/extend mode](./keymap.md#select--extend-mode): For making selections and performing operations on them. Access by typing `v` in normal mode.
|
||||
- `"ay` - Yank the current selection to register `a`.
|
||||
- `"op` - Paste the text in register `o` after the selection.
|
||||
|
||||
## Buffers
|
||||
If there is a selected register before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:
|
||||
|
||||
Buffers are in-memory representations of files. You can have multiple buffers open at once. Use [pickers](./pickers.md) or commands like `:buffer-next` and `:buffer-previous` to open buffers or switch between them.
|
||||
- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
|
||||
- `"md` - Store the selection in register `m` and delete it.
|
||||
|
||||
## Selection-first editing
|
||||
### Special Registers
|
||||
|
||||
Inspired by [Kakoune](http://kakoune.org/), Helix follows the `selection → action` model. This means that whatever you are going to act on (a word, a paragraph, a line, etc.) is selected first and the action itself (delete, change, yank, etc.) comes second. A cursor is simply a single width selection.
|
||||
| Register character | Contains |
|
||||
| --- | --- |
|
||||
| `/` | Last search |
|
||||
| `:` | Last executed command |
|
||||
| `"` | Last yanked text |
|
||||
| `_` | Black hole |
|
||||
|
||||
## Multiple selections
|
||||
> There is no special register for copying to system clipboard, instead special commands and keybindings are provided. See the [keymap](keymap.md#space-mode) for the specifics.
|
||||
> The black hole register works as a no-op register, meaning no data will be written to / read from it.
|
||||
|
||||
Also inspired by Kakoune, multiple selections are a core mode of interaction in Helix. For example, the standard way of replacing multiple instances of a word is to first select all instances (so there is one selection per instance) and then use the change action (`c`) to edit them all at the same time.
|
||||
## Surround
|
||||
|
||||
## Motions
|
||||
Functionality similar to [vim-surround](https://github.com/tpope/vim-surround) is built into
|
||||
helix. The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
|
||||
|
||||
Motions are commands that move the cursor or modify selections. They're used for navigation and text manipulation. Examples include `w` to move to the next word, or `f` to find a character. See the [Movement](./keymap.md#movement) section of the keymap for more motions.
|
||||

|
||||
|
||||
- `ms` - Add surround characters
|
||||
- `mr` - Replace surround characters
|
||||
- `md` - Delete surround characters
|
||||
|
||||
`ms` acts on a selection, so select the text first and use `ms<char>`. `mr` and `md` work
|
||||
on the closest pairs found and selections are not required; use counts to act in outer pairs.
|
||||
|
||||
It can also act on multiple selections (yay!). For example, to change every occurrence of `(use)` to `[use]`:
|
||||
|
||||
- `%` to select the whole file
|
||||
- `s` to split the selections on a search term
|
||||
- Input `use` and hit Enter
|
||||
- `mr([` to replace the parens with square brackets
|
||||
|
||||
Multiple characters are currently not supported, but planned.
|
||||
|
||||
## Textobjects
|
||||
|
||||
Currently supported: `word`, `surround`, `function`, `class`, `parameter`.
|
||||
|
||||

|
||||

|
||||
|
||||
- `ma` - Select around the object (`va` in vim, `<alt-a>` in kakoune)
|
||||
- `mi` - Select inside the object (`vi` in vim, `<alt-i>` in kakoune)
|
||||
|
||||
| Key after `mi` or `ma` | Textobject selected |
|
||||
| --- | --- |
|
||||
| `w` | Word |
|
||||
| `W` | WORD |
|
||||
| `(`, `[`, `'`, etc | Specified surround pairs |
|
||||
| `f` | Function |
|
||||
| `c` | Class |
|
||||
| `a` | Argument/parameter |
|
||||
| `o` | Comment |
|
||||
|
||||
> NOTE: `f`, `c`, etc need a tree-sitter grammar active for the current
|
||||
document and a special tree-sitter query file to work properly. [Only
|
||||
some grammars][lang-support] currently have the query file implemented.
|
||||
Contributions are welcome!
|
||||
|
||||
## Tree-sitter Textobject Based Navigation
|
||||
|
||||
Navigating between functions, classes, parameters, etc is made
|
||||
possible by leveraging tree-sitter and textobjects queries. For
|
||||
example to move to the next function use `]f`, to move to previous
|
||||
class use `[c`, and so on.
|
||||
|
||||
![tree-sitter-nav-demo][tree-sitter-nav-demo]
|
||||
|
||||
See the [unimpaired][unimpaired-keybinds] section of the keybind
|
||||
documentation for the full reference.
|
||||
|
||||
> NOTE: This feature is dependent on tree-sitter based textobjects
|
||||
and therefore requires the corresponding query file to work properly.
|
||||
|
||||
[lang-support]: ./lang-support.md
|
||||
[unimpaired-keybinds]: ./keymap.md#unimpaired
|
||||
[tree-sitter-nav-demo]: https://user-images.githubusercontent.com/23398472/152332550-7dfff043-36a2-4aec-b8f2-77c13eb56d6f.gif
|
||||
|
660
book/theme/book.js
Normal file
660
book/theme/book.js
Normal file
@@ -0,0 +1,660 @@
|
||||
"use strict";
|
||||
|
||||
// Fix back button cache problem
|
||||
window.onunload = function () { };
|
||||
|
||||
// Global variable, shared between modules
|
||||
function playground_text(playground) {
|
||||
let code_block = playground.querySelector("code");
|
||||
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
return editor.getValue();
|
||||
} else {
|
||||
return code_block.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
(function codeSnippets() {
|
||||
function fetch_with_timeout(url, options, timeout = 6000) {
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
|
||||
]);
|
||||
}
|
||||
|
||||
var playgrounds = Array.from(document.querySelectorAll(".playground"));
|
||||
if (playgrounds.length > 0) {
|
||||
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
// get list of crates available in the rust playground
|
||||
let playground_crates = response.crates.map(item => item["id"]);
|
||||
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
|
||||
});
|
||||
}
|
||||
|
||||
function handle_crate_list_update(playground_block, playground_crates) {
|
||||
// update the play buttons after receiving the response
|
||||
update_play_button(playground_block, playground_crates);
|
||||
|
||||
// and install on change listener to dynamically update ACE editors
|
||||
if (window.ace) {
|
||||
let code_block = playground_block.querySelector("code");
|
||||
if (code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.addEventListener("change", function (e) {
|
||||
update_play_button(playground_block, playground_crates);
|
||||
});
|
||||
// add Ctrl-Enter command to execute rust code
|
||||
editor.commands.addCommand({
|
||||
name: "run",
|
||||
bindKey: {
|
||||
win: "Ctrl-Enter",
|
||||
mac: "Ctrl-Enter"
|
||||
},
|
||||
exec: _editor => run_rust_code(playground_block)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updates the visibility of play button based on `no_run` class and
|
||||
// used crates vs ones available on http://play.rust-lang.org
|
||||
function update_play_button(pre_block, playground_crates) {
|
||||
var play_button = pre_block.querySelector(".play-button");
|
||||
|
||||
// skip if code is `no_run`
|
||||
if (pre_block.querySelector('code').classList.contains("no_run")) {
|
||||
play_button.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// get list of `extern crate`'s from snippet
|
||||
var txt = playground_text(pre_block);
|
||||
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
|
||||
var snippet_crates = [];
|
||||
var item;
|
||||
while (item = re.exec(txt)) {
|
||||
snippet_crates.push(item[1]);
|
||||
}
|
||||
|
||||
// check if all used crates are available on play.rust-lang.org
|
||||
var all_available = snippet_crates.every(function (elem) {
|
||||
return playground_crates.indexOf(elem) > -1;
|
||||
});
|
||||
|
||||
if (all_available) {
|
||||
play_button.classList.remove("hidden");
|
||||
} else {
|
||||
play_button.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function run_rust_code(code_block) {
|
||||
var result_block = code_block.querySelector(".result");
|
||||
if (!result_block) {
|
||||
result_block = document.createElement('code');
|
||||
result_block.className = 'result hljs language-bash';
|
||||
|
||||
code_block.append(result_block);
|
||||
}
|
||||
|
||||
let text = playground_text(code_block);
|
||||
let classes = code_block.querySelector('code').classList;
|
||||
let has_2018 = classes.contains("edition2018");
|
||||
let edition = has_2018 ? "2018" : "2015";
|
||||
|
||||
var params = {
|
||||
version: "stable",
|
||||
optimize: "0",
|
||||
code: text,
|
||||
edition: edition
|
||||
};
|
||||
|
||||
if (text.indexOf("#![feature") !== -1) {
|
||||
params.version = "nightly";
|
||||
}
|
||||
|
||||
result_block.innerText = "Running...";
|
||||
|
||||
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => result_block.innerText = response.result)
|
||||
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
|
||||
}
|
||||
|
||||
// Syntax highlighting Configuration
|
||||
hljs.configure({
|
||||
tabReplace: ' ', // 4 spaces
|
||||
languages: [], // Languages used for auto-detection
|
||||
});
|
||||
|
||||
let code_nodes = Array
|
||||
.from(document.querySelectorAll('code'))
|
||||
// Don't highlight `inline code` blocks in headers.
|
||||
.filter(function (node) {return !node.parentElement.classList.contains("header"); });
|
||||
|
||||
if (window.ace) {
|
||||
// language-rust class needs to be removed for editable
|
||||
// blocks or highlightjs will capture events
|
||||
Array
|
||||
.from(document.querySelectorAll('code.editable'))
|
||||
.forEach(function (block) { block.classList.remove('language-rust'); });
|
||||
|
||||
Array
|
||||
.from(document.querySelectorAll('code:not(.editable)'))
|
||||
.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
} else {
|
||||
code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
}
|
||||
|
||||
// Adding the hljs class gives code blocks the color css
|
||||
// even if highlighting doesn't apply
|
||||
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
|
||||
|
||||
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
|
||||
|
||||
var lines = Array.from(block.querySelectorAll('.boring'));
|
||||
// If no lines were hidden, return
|
||||
if (!lines.length) { return; }
|
||||
block.classList.add("hide-boring");
|
||||
|
||||
var buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
||||
|
||||
// add expand button
|
||||
var pre_block = block.parentNode;
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
|
||||
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
||||
if (e.target.classList.contains('fa-eye')) {
|
||||
e.target.classList.remove('fa-eye');
|
||||
e.target.classList.add('fa-eye-slash');
|
||||
e.target.title = 'Hide lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.remove('hide-boring');
|
||||
} else if (e.target.classList.contains('fa-eye-slash')) {
|
||||
e.target.classList.remove('fa-eye-slash');
|
||||
e.target.classList.add('fa-eye');
|
||||
e.target.title = 'Show hidden lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.add('hide-boring');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (window.playground_copyable) {
|
||||
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
||||
var pre_block = block.parentNode;
|
||||
if (!pre_block.classList.contains('playground')) {
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var clipButton = document.createElement('button');
|
||||
clipButton.className = 'fa fa-copy clip-button';
|
||||
clipButton.title = 'Copy to clipboard';
|
||||
clipButton.setAttribute('aria-label', clipButton.title);
|
||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||
|
||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process playground code blocks
|
||||
Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
|
||||
// Add play button
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var runCodeButton = document.createElement('button');
|
||||
runCodeButton.className = 'fa fa-play play-button';
|
||||
runCodeButton.hidden = true;
|
||||
runCodeButton.title = 'Run this code';
|
||||
runCodeButton.setAttribute('aria-label', runCodeButton.title);
|
||||
|
||||
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
||||
runCodeButton.addEventListener('click', function (e) {
|
||||
run_rust_code(pre_block);
|
||||
});
|
||||
|
||||
if (window.playground_copyable) {
|
||||
var copyCodeClipboardButton = document.createElement('button');
|
||||
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
||||
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||
|
||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||
}
|
||||
|
||||
let code_block = pre_block.querySelector("code");
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
var undoChangesButton = document.createElement('button');
|
||||
undoChangesButton.className = 'fa fa-history reset-button';
|
||||
undoChangesButton.title = 'Undo changes';
|
||||
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
|
||||
|
||||
buttons.insertBefore(undoChangesButton, buttons.firstChild);
|
||||
|
||||
undoChangesButton.addEventListener('click', function () {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.setValue(editor.originalCode);
|
||||
editor.clearSelection();
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function themes() {
|
||||
var html = document.querySelector('html');
|
||||
var themeToggleButton = document.getElementById('theme-toggle');
|
||||
var themePopup = document.getElementById('theme-list');
|
||||
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
|
||||
var stylesheets = {
|
||||
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
|
||||
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
|
||||
highlight: document.querySelector("[href$='highlight.css']"),
|
||||
};
|
||||
|
||||
function showThemes() {
|
||||
themePopup.style.display = 'block';
|
||||
themeToggleButton.setAttribute('aria-expanded', true);
|
||||
themePopup.querySelector("button#" + get_theme()).focus();
|
||||
}
|
||||
|
||||
function hideThemes() {
|
||||
themePopup.style.display = 'none';
|
||||
themeToggleButton.setAttribute('aria-expanded', false);
|
||||
themeToggleButton.focus();
|
||||
}
|
||||
|
||||
function get_theme() {
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
||||
if (theme === null || theme === undefined) {
|
||||
return default_theme;
|
||||
} else {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
function set_theme(theme, store = true) {
|
||||
let ace_theme;
|
||||
|
||||
if (theme == 'coal' || theme == 'navy') {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = false;
|
||||
stylesheets.highlight.disabled = true;
|
||||
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else if (theme == 'ayu') {
|
||||
stylesheets.ayuHighlight.disabled = false;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = true;
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = false;
|
||||
ace_theme = "ace/theme/dawn";
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
|
||||
}, 1);
|
||||
|
||||
if (window.ace && window.editors) {
|
||||
window.editors.forEach(function (editor) {
|
||||
editor.setTheme(ace_theme);
|
||||
});
|
||||
}
|
||||
|
||||
var previousTheme = get_theme();
|
||||
|
||||
if (store) {
|
||||
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
||||
}
|
||||
|
||||
html.classList.remove(previousTheme);
|
||||
html.classList.add(theme);
|
||||
}
|
||||
|
||||
// Set theme
|
||||
var theme = get_theme();
|
||||
|
||||
set_theme(theme, false);
|
||||
|
||||
themeToggleButton.addEventListener('click', function () {
|
||||
if (themePopup.style.display === 'block') {
|
||||
hideThemes();
|
||||
} else {
|
||||
showThemes();
|
||||
}
|
||||
});
|
||||
|
||||
themePopup.addEventListener('click', function (e) {
|
||||
var theme = e.target.id || e.target.parentElement.id;
|
||||
set_theme(theme);
|
||||
});
|
||||
|
||||
themePopup.addEventListener('focusout', function(e) {
|
||||
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
||||
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
||||
document.addEventListener('click', function(e) {
|
||||
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (!themePopup.contains(e.target)) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
hideThemes();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.previousElementSibling) {
|
||||
li.previousElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.nextElementSibling) {
|
||||
li.nextElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:first-child button').focus();
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:last-child button').focus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function sidebar() {
|
||||
var html = document.querySelector("html");
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var sidebarLinks = document.querySelectorAll('#sidebar a');
|
||||
var sidebarToggleButton = document.getElementById("sidebar-toggle");
|
||||
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
|
||||
var firstContact = null;
|
||||
|
||||
function showSidebar() {
|
||||
html.classList.remove('sidebar-hidden')
|
||||
html.classList.add('sidebar-visible');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', 0);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', true);
|
||||
sidebar.setAttribute('aria-hidden', false);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
|
||||
}
|
||||
|
||||
|
||||
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
|
||||
|
||||
function toggleSection(ev) {
|
||||
ev.currentTarget.parentElement.classList.toggle('expanded');
|
||||
}
|
||||
|
||||
Array.from(sidebarAnchorToggles).forEach(function (el) {
|
||||
el.addEventListener('click', toggleSection);
|
||||
});
|
||||
|
||||
function hideSidebar() {
|
||||
html.classList.remove('sidebar-visible')
|
||||
html.classList.add('sidebar-hidden');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', -1);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', false);
|
||||
sidebar.setAttribute('aria-hidden', true);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
|
||||
}
|
||||
|
||||
// Toggle sidebar
|
||||
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
|
||||
if (html.classList.contains("sidebar-hidden")) {
|
||||
var current_width = parseInt(
|
||||
document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
|
||||
if (current_width < 150) {
|
||||
document.documentElement.style.setProperty('--sidebar-width', '150px');
|
||||
}
|
||||
showSidebar();
|
||||
} else if (html.classList.contains("sidebar-visible")) {
|
||||
hideSidebar();
|
||||
} else {
|
||||
if (getComputedStyle(sidebar)['transform'] === 'none') {
|
||||
hideSidebar();
|
||||
} else {
|
||||
showSidebar();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
|
||||
|
||||
function initResize(e) {
|
||||
window.addEventListener('mousemove', resize, false);
|
||||
window.addEventListener('mouseup', stopResize, false);
|
||||
html.classList.add('sidebar-resizing');
|
||||
}
|
||||
function resize(e) {
|
||||
var pos = (e.clientX - sidebar.offsetLeft);
|
||||
if (pos < 20) {
|
||||
hideSidebar();
|
||||
} else {
|
||||
if (html.classList.contains("sidebar-hidden")) {
|
||||
showSidebar();
|
||||
}
|
||||
pos = Math.min(pos, window.innerWidth - 100);
|
||||
document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
|
||||
}
|
||||
}
|
||||
//on mouseup remove windows functions mousemove & mouseup
|
||||
function stopResize(e) {
|
||||
html.classList.remove('sidebar-resizing');
|
||||
window.removeEventListener('mousemove', resize, false);
|
||||
window.removeEventListener('mouseup', stopResize, false);
|
||||
}
|
||||
|
||||
document.addEventListener('touchstart', function (e) {
|
||||
firstContact = {
|
||||
x: e.touches[0].clientX,
|
||||
time: Date.now()
|
||||
};
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchmove', function (e) {
|
||||
if (!firstContact)
|
||||
return;
|
||||
|
||||
var curX = e.touches[0].clientX;
|
||||
var xDiff = curX - firstContact.x,
|
||||
tDiff = Date.now() - firstContact.time;
|
||||
|
||||
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
|
||||
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
|
||||
showSidebar();
|
||||
else if (xDiff < 0 && curX < 300)
|
||||
hideSidebar();
|
||||
|
||||
firstContact = null;
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
// Scroll sidebar to current active section
|
||||
var activeSection = document.getElementById("sidebar").querySelector(".active");
|
||||
if (activeSection) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
|
||||
activeSection.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
})();
|
||||
|
||||
(function chapterNavigation() {
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (window.search && window.search.hasFocus()) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
var nextButton = document.querySelector('.nav-chapters.next');
|
||||
if (nextButton) {
|
||||
window.location.href = nextButton.href;
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
var previousButton = document.querySelector('.nav-chapters.previous');
|
||||
if (previousButton) {
|
||||
window.location.href = previousButton.href;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function clipboard() {
|
||||
var clipButtons = document.querySelectorAll('.clip-button');
|
||||
|
||||
function hideTooltip(elem) {
|
||||
elem.firstChild.innerText = "";
|
||||
elem.className = 'fa fa-copy clip-button';
|
||||
}
|
||||
|
||||
function showTooltip(elem, msg) {
|
||||
elem.firstChild.innerText = msg;
|
||||
elem.className = 'fa fa-copy tooltipped';
|
||||
}
|
||||
|
||||
var clipboardSnippets = new ClipboardJS('.clip-button', {
|
||||
text: function (trigger) {
|
||||
hideTooltip(trigger);
|
||||
let playground = trigger.closest("pre");
|
||||
return playground_text(playground);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(clipButtons).forEach(function (clipButton) {
|
||||
clipButton.addEventListener('mouseout', function (e) {
|
||||
hideTooltip(e.currentTarget);
|
||||
});
|
||||
});
|
||||
|
||||
clipboardSnippets.on('success', function (e) {
|
||||
e.clearSelection();
|
||||
showTooltip(e.trigger, "Copied!");
|
||||
});
|
||||
|
||||
clipboardSnippets.on('error', function (e) {
|
||||
showTooltip(e.trigger, "Clipboard error!");
|
||||
});
|
||||
})();
|
||||
|
||||
(function scrollToTop () {
|
||||
var menuTitle = document.querySelector('.menu-title');
|
||||
|
||||
menuTitle.addEventListener('click', function () {
|
||||
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
})();
|
||||
|
||||
(function controllMenu() {
|
||||
var menu = document.getElementById('menu-bar');
|
||||
|
||||
(function controllPosition() {
|
||||
var scrollTop = document.scrollingElement.scrollTop;
|
||||
var prevScrollTop = scrollTop;
|
||||
var minMenuY = -menu.clientHeight - 50;
|
||||
// When the script loads, the page can be at any scroll (e.g. if you reforesh it).
|
||||
menu.style.top = scrollTop + 'px';
|
||||
// Same as parseInt(menu.style.top.slice(0, -2), but faster
|
||||
var topCache = menu.style.top.slice(0, -2);
|
||||
menu.classList.remove('sticky');
|
||||
var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
|
||||
document.addEventListener('scroll', function () {
|
||||
scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
|
||||
// `null` means that it doesn't need to be updated
|
||||
var nextSticky = null;
|
||||
var nextTop = null;
|
||||
var scrollDown = scrollTop > prevScrollTop;
|
||||
var menuPosAbsoluteY = topCache - scrollTop;
|
||||
if (scrollDown) {
|
||||
nextSticky = false;
|
||||
if (menuPosAbsoluteY > 0) {
|
||||
nextTop = prevScrollTop;
|
||||
}
|
||||
} else {
|
||||
if (menuPosAbsoluteY > 0) {
|
||||
nextSticky = true;
|
||||
} else if (menuPosAbsoluteY < minMenuY) {
|
||||
nextTop = prevScrollTop + minMenuY;
|
||||
}
|
||||
}
|
||||
if (nextSticky === true && stickyCache === false) {
|
||||
menu.classList.add('sticky');
|
||||
stickyCache = true;
|
||||
} else if (nextSticky === false && stickyCache === true) {
|
||||
menu.classList.remove('sticky');
|
||||
stickyCache = false;
|
||||
}
|
||||
if (nextTop !== null) {
|
||||
menu.style.top = nextTop + 'px';
|
||||
topCache = nextTop;
|
||||
}
|
||||
prevScrollTop = scrollTop;
|
||||
}, { passive: true });
|
||||
})();
|
||||
(function controllBorder() {
|
||||
menu.classList.remove('bordered');
|
||||
document.addEventListener('scroll', function () {
|
||||
if (menu.offsetTop === 0) {
|
||||
menu.classList.remove('bordered');
|
||||
} else {
|
||||
menu.classList.add('bordered');
|
||||
}
|
||||
}, { passive: true });
|
||||
})();
|
||||
})();
|
499
book/theme/css/chrome.css
Normal file
499
book/theme/css/chrome.css
Normal file
@@ -0,0 +1,499 @@
|
||||
/* CSS for UI elements (a.k.a. chrome) */
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: var(--bg);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
html {
|
||||
scrollbar-color: var(--scrollbar) var(--bg);
|
||||
}
|
||||
#searchresults a,
|
||||
.content a:link,
|
||||
a:visited,
|
||||
a > .hljs {
|
||||
color: var(--links);
|
||||
}
|
||||
.content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Menu Bar */
|
||||
|
||||
#menu-bar,
|
||||
#menu-bar-hover-placeholder {
|
||||
z-index: 101;
|
||||
margin: auto calc(0px - var(--page-padding));
|
||||
}
|
||||
#menu-bar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: var(--bg);
|
||||
border-bottom-color: var(--bg);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
#menu-bar.sticky,
|
||||
.js #menu-bar-hover-placeholder:hover + #menu-bar,
|
||||
.js #menu-bar:hover,
|
||||
.js.sidebar-visible #menu-bar {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0 !important;
|
||||
}
|
||||
#menu-bar-hover-placeholder {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
height: var(--menu-bar-height);
|
||||
}
|
||||
#menu-bar.bordered {
|
||||
border-bottom-color: var(--table-border-color);
|
||||
}
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
position: relative;
|
||||
padding: 0 8px;
|
||||
z-index: 10;
|
||||
line-height: var(--menu-bar-height);
|
||||
cursor: pointer;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
@media only screen and (max-width: 420px) {
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.icon-button i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
margin: 0 15px;
|
||||
}
|
||||
.right-buttons a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
display: flex;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.no-js .left-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
display: inline-block;
|
||||
font-weight: 200;
|
||||
font-size: 2.4rem;
|
||||
line-height: var(--menu-bar-height);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.js .menu-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-bar,
|
||||
.menu-bar:visited,
|
||||
.nav-chapters,
|
||||
.nav-chapters:visited,
|
||||
.mobile-nav-chapters,
|
||||
.mobile-nav-chapters:visited,
|
||||
.menu-bar .icon-button,
|
||||
.menu-bar a i {
|
||||
color: var(--icons);
|
||||
}
|
||||
|
||||
.menu-bar i:hover,
|
||||
.menu-bar .icon-button:hover,
|
||||
.nav-chapters:hover,
|
||||
.mobile-nav-chapters i:hover {
|
||||
color: var(--icons-hover);
|
||||
}
|
||||
|
||||
/* Nav Icons */
|
||||
|
||||
.nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
max-width: 150px;
|
||||
min-width: 90px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
.nav-chapters:hover {
|
||||
text-decoration: none;
|
||||
background-color: var(--theme-hover);
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
margin-top: 50px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 90px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.previous {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.next {
|
||||
float: right;
|
||||
right: var(--page-padding);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1080px) {
|
||||
.nav-wide-wrapper { display: none; }
|
||||
.nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1380px) {
|
||||
.sidebar-visible .nav-wide-wrapper { display: none; }
|
||||
.sidebar-visible .nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
|
||||
:not(pre) > .hljs {
|
||||
display: inline;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:not(pre):not(a):not(td):not(p) > .hljs {
|
||||
color: var(--inline-code-color);
|
||||
overflow-x: initial;
|
||||
}
|
||||
|
||||
a:hover > .hljs {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
pre > .buttons {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
|
||||
color: var(--sidebar-fg);
|
||||
cursor: pointer;
|
||||
}
|
||||
pre > .buttons :hover {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
pre > .buttons i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
pre > .buttons button {
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
pre > .result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
|
||||
#searchresults a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
mark {
|
||||
border-radius: 2px;
|
||||
padding: 0 3px 1px 3px;
|
||||
margin: 0 -3px -1px -3px;
|
||||
background-color: var(--search-mark-bg);
|
||||
transition: background-color 300ms linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mark.fade-out {
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.searchbar-outer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
|
||||
#searchbar {
|
||||
width: 100%;
|
||||
margin: 5px auto 0px auto;
|
||||
padding: 10px 16px;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
border: 1px solid var(--searchbar-border-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--searchbar-bg);
|
||||
color: var(--searchbar-fg);
|
||||
}
|
||||
#searchbar:focus,
|
||||
#searchbar.active {
|
||||
box-shadow: 0 0 3px var(--searchbar-shadow-color);
|
||||
}
|
||||
|
||||
.searchresults-header {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
padding: 18px 0 0 5px;
|
||||
color: var(--searchresults-header-fg);
|
||||
}
|
||||
|
||||
.searchresults-outer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
border-bottom: 1px dashed var(--searchresults-border-color);
|
||||
}
|
||||
|
||||
ul#searchresults {
|
||||
list-style: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
ul#searchresults li {
|
||||
margin: 10px 0px;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
ul#searchresults li.focus {
|
||||
background-color: var(--searchresults-li-bg);
|
||||
}
|
||||
ul#searchresults span.teaser {
|
||||
display: block;
|
||||
clear: both;
|
||||
margin: 5px 0 0 20px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
ul#searchresults span.teaser em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: var(--sidebar-width);
|
||||
font-size: 0.875em;
|
||||
box-sizing: border-box;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: contain;
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
.sidebar-resizing {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.js:not(.sidebar-resizing) .sidebar {
|
||||
transition: transform 0.3s; /* Animation: slide away */
|
||||
}
|
||||
.sidebar code {
|
||||
line-height: 2em;
|
||||
}
|
||||
.sidebar .sidebar-scrollbox {
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.sidebar .sidebar-resize-handle {
|
||||
position: absolute;
|
||||
cursor: col-resize;
|
||||
width: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.js .sidebar .sidebar-resize-handle {
|
||||
cursor: col-resize;
|
||||
width: 5px;
|
||||
}
|
||||
.sidebar-hidden .sidebar {
|
||||
transform: translateX(calc(0px - var(--sidebar-width)));
|
||||
}
|
||||
.sidebar::-webkit-scrollbar {
|
||||
background: var(--sidebar-bg);
|
||||
}
|
||||
.sidebar::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
|
||||
.sidebar-visible .page-wrapper {
|
||||
transform: translateX(var(--sidebar-width));
|
||||
}
|
||||
@media only screen and (min-width: 620px) {
|
||||
.sidebar-visible .page-wrapper {
|
||||
transform: none;
|
||||
margin-left: var(--sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
.chapter {
|
||||
list-style: none outside none;
|
||||
padding-left: 0;
|
||||
margin: .25rem 0;
|
||||
}
|
||||
|
||||
.chapter ol {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter li {
|
||||
display: flex;
|
||||
color: var(--sidebar-non-existant);
|
||||
}
|
||||
.chapter li a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
|
||||
.chapter li a:hover {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li a.active {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li > a.toggle {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
padding: 0 10px;
|
||||
user-select: none;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.chapter li > a.toggle div {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
/* collapse the section */
|
||||
.chapter li:not(.expanded) + li > ol {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chapter li.chapter-item {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.chapter .section li.chapter-item {
|
||||
padding: .5rem .5rem 0 .5rem;
|
||||
}
|
||||
|
||||
.chapter li.expanded > a.toggle div {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
.chapter .spacer {
|
||||
background-color: var(--sidebar-spacer);
|
||||
}
|
||||
|
||||
@media (-moz-touch-enabled: 1), (pointer: coarse) {
|
||||
.chapter li a { padding: 5px 0; }
|
||||
.spacer { margin: 10px 0; }
|
||||
}
|
||||
|
||||
.section {
|
||||
list-style: none outside none;
|
||||
padding-left: 2rem;
|
||||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
/* Theme Menu Popup */
|
||||
|
||||
.theme-popup {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: var(--menu-bar-height);
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
color: var(--fg);
|
||||
background: var(--theme-popup-bg);
|
||||
border: 1px solid var(--theme-popup-border);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: none;
|
||||
}
|
||||
.theme-popup .default {
|
||||
color: var(--icons);
|
||||
}
|
||||
.theme-popup .theme {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px 10px;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
.theme-popup .theme:hover {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
.theme-popup .theme:hover:first-child,
|
||||
.theme-popup .theme:hover:last-child {
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
}
|
233
book/theme/css/general.css
Normal file
233
book/theme/css/general.css
Normal file
@@ -0,0 +1,233 @@
|
||||
/* Base styles and content styles */
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
:root {
|
||||
/* Browser default font-size is 16px, this way 1 rem = 10px */
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
/* TODO: replace with self hosted fonts */
|
||||
|
||||
html {
|
||||
font-family: "Inter", sans-serif;
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
/* @supports (font-variation-settings: normal) { */
|
||||
/* html { font-family: 'Inter var', sans-serif; } */
|
||||
/* } */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
|
||||
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
|
||||
}
|
||||
|
||||
/* Don't change font size in headers. */
|
||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
||||
font-size: unset;
|
||||
}
|
||||
|
||||
.left { float: left; }
|
||||
.right { float: right; }
|
||||
.boring { opacity: 0.6; }
|
||||
.hide-boring .boring { display: none; }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
h2, h3 { margin-top: 2.5em; }
|
||||
h4, h5 { margin-top: 2em; }
|
||||
|
||||
.header + .header h3,
|
||||
.header + .header h4,
|
||||
.header + .header h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h1:target::before,
|
||||
h2:target::before,
|
||||
h3:target::before,
|
||||
h4:target::before,
|
||||
h5:target::before,
|
||||
h6:target::before {
|
||||
display: inline-block;
|
||||
content: "»";
|
||||
margin-left: -30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
/* This is broken on Safari as of version 14, but is fixed
|
||||
in Safari Technology Preview 117 which I think will be Safari 14.2.
|
||||
https://bugs.webkit.org/show_bug.cgi?id=218076
|
||||
*/
|
||||
:target {
|
||||
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
|
||||
}
|
||||
|
||||
.page {
|
||||
outline: 0;
|
||||
padding: 0 var(--page-padding);
|
||||
margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
|
||||
}
|
||||
.page-wrapper {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.js:not(.sidebar-resizing) .page-wrapper {
|
||||
transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
padding: 0 15px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
.content main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
|
||||
|
||||
/* 2 1.75 1.5 1.25 1 .875 */
|
||||
|
||||
.content h1 { font-size: 2em }
|
||||
.content h2 { font-size: 1.75em }
|
||||
.content h3 { font-size: 1.5em }
|
||||
.content h4 { font-size: 1.25em }
|
||||
.content h5 { font-size: 1em }
|
||||
.content h6 { font-size: .875em }
|
||||
|
||||
.content h1, .content h2, .content h3, .content h4 {
|
||||
font-weight: 500;
|
||||
margin-top: 1.275em;
|
||||
margin-bottom: .875em;
|
||||
}
|
||||
.content p, .content ol, .content ul, .content table {
|
||||
margin-top: 0;
|
||||
margin-bottom: .875em;
|
||||
}
|
||||
|
||||
.content ul li {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.content ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
.content ul ul, .content ol ul {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.content li p {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.content p { line-height: 1.45em; }
|
||||
.content ol { line-height: 1.45em; }
|
||||
.content ul { line-height: 1.45em; }
|
||||
.content a { text-decoration: none; }
|
||||
.content a:hover { text-decoration: underline; }
|
||||
.content img { max-width: 100%; }
|
||||
.content .header:link,
|
||||
.content .header:visited {
|
||||
color: var(--fg);
|
||||
color: var(--heading-fg);
|
||||
}
|
||||
.content .header:link,
|
||||
.content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
table td {
|
||||
padding: .75rem;
|
||||
width: auto;
|
||||
}
|
||||
table thead {
|
||||
background: var(--table-header-bg);
|
||||
}
|
||||
table thead td {
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
}
|
||||
table thead th {
|
||||
padding: .75rem;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
width: auto;
|
||||
}
|
||||
table thead tr {
|
||||
border-bottom: 2px var(--table-border-color) solid;
|
||||
}
|
||||
table tbody tr {
|
||||
border-bottom: 1px var(--table-border-line) solid;
|
||||
}
|
||||
/* Alternate background colors for rows */
|
||||
table tbody tr:nth-child(2n) {
|
||||
/* background: var(--table-alternate-bg); */
|
||||
}
|
||||
|
||||
|
||||
blockquote {
|
||||
margin: 1.5rem 0;
|
||||
padding: 1rem 1.5rem;
|
||||
color: var(--fg);
|
||||
opacity: .9;
|
||||
background-color: var(--quote-bg);
|
||||
border-left: 4px solid var(--quote-border);
|
||||
}
|
||||
blockquote *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
:not(.footnote-definition) + .footnote-definition,
|
||||
.footnote-definition + :not(.footnote-definition) {
|
||||
margin-top: 2em;
|
||||
}
|
||||
.footnote-definition {
|
||||
font-size: 0.9em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.footnote-definition p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
|
||||
left: -8px; /* Half of the width of the icon */
|
||||
top: -35px;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px;
|
||||
margin: 5px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.tooltipped .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.chapter li.part-title {
|
||||
color: var(--sidebar-fg);
|
||||
margin: 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-no-output {
|
||||
font-style: italic;
|
||||
}
|
54
book/theme/css/print.css
Normal file
54
book/theme/css/print.css
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
#sidebar,
|
||||
#menu-bar,
|
||||
.nav-chapters,
|
||||
.mobile-nav-chapters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#page-wrapper.page-wrapper {
|
||||
transform: none;
|
||||
margin-left: 0px;
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
#content {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #666666;
|
||||
border-radius: 5px;
|
||||
|
||||
/* Force background to be printed in Chrome */
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
pre > .buttons {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
a, a:visited, a:active, a:hover {
|
||||
color: #4183c4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
page-break-inside: avoid;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.fa {
|
||||
display: none !important;
|
||||
}
|
342
book/theme/css/variables.css
Normal file
342
book/theme/css/variables.css
Normal file
@@ -0,0 +1,342 @@
|
||||
|
||||
/* Globals */
|
||||
|
||||
:root {
|
||||
--sidebar-width: 300px;
|
||||
--page-padding: 15px;
|
||||
--content-max-width: 750px;
|
||||
--menu-bar-height: 50px;
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
|
||||
.ayu {
|
||||
--bg: hsl(210, 25%, 8%);
|
||||
--fg: #c5c5c5;
|
||||
|
||||
--sidebar-bg: #14191f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #5c6773;
|
||||
--sidebar-active: #ffb454;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #0096cf;
|
||||
|
||||
--inline-code-color: #ffb454;
|
||||
|
||||
--theme-popup-bg: #14191f;
|
||||
--theme-popup-border: #5c6773;
|
||||
--theme-hover: #191f26;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(210, 25%, 13%);
|
||||
--table-header-bg: hsl(210, 25%, 28%);
|
||||
--table-alternate-bg: hsl(210, 25%, 11%);
|
||||
|
||||
--searchbar-border-color: #848484;
|
||||
--searchbar-bg: #424242;
|
||||
--searchbar-fg: #fff;
|
||||
--searchbar-shadow-color: #d4c89f;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #252932;
|
||||
--search-mark-bg: #e3b171;
|
||||
}
|
||||
|
||||
.coal {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
}
|
||||
|
||||
.light {
|
||||
--bg: hsl(0, 0%, 100%);
|
||||
--fg: hsl(0, 0%, 0%);
|
||||
|
||||
--sidebar-bg: #fafafa;
|
||||
--sidebar-fg: hsl(0, 0%, 0%);
|
||||
--sidebar-non-existant: #aaaaaa;
|
||||
--sidebar-active: #1f1fff;
|
||||
--sidebar-spacer: #f4f4f4;
|
||||
|
||||
--scrollbar: #8F8F8F;
|
||||
|
||||
--icons: #747474;
|
||||
--icons-hover: #000000;
|
||||
|
||||
--links: #20609f;
|
||||
|
||||
--inline-code-color: #301900;
|
||||
|
||||
--theme-popup-bg: #fafafa;
|
||||
--theme-popup-border: #cccccc;
|
||||
--theme-hover: #e6e6e6;
|
||||
|
||||
--quote-bg: hsl(197, 37%, 96%);
|
||||
--quote-border: hsl(197, 37%, 91%);
|
||||
|
||||
--table-border-color: hsl(0, 0%, 95%);
|
||||
--table-header-bg: hsl(0, 0%, 80%);
|
||||
--table-alternate-bg: hsl(0, 0%, 97%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #e4f2fe;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.navy {
|
||||
--bg: hsl(226, 23%, 11%);
|
||||
--fg: #bcbdd0;
|
||||
|
||||
--sidebar-bg: #282d3f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505274;
|
||||
--sidebar-active: #2b79a2;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
--theme-hover: #282e40;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(226, 23%, 16%);
|
||||
--table-header-bg: hsl(226, 23%, 31%);
|
||||
--table-alternate-bg: hsl(226, 23%, 14%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #aeaec6;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #5f5f71;
|
||||
--searchresults-border-color: #5c5c68;
|
||||
--searchresults-li-bg: #242430;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.rust {
|
||||
--bg: hsl(60, 9%, 87%);
|
||||
--fg: #262625;
|
||||
|
||||
--sidebar-bg: #3b2e2a;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #e69f67;
|
||||
--sidebar-spacer: #45373a;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #262625;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #6e6b5e;
|
||||
|
||||
--theme-popup-bg: #e1e1db;
|
||||
--theme-popup-border: #b38f6b;
|
||||
--theme-hover: #99908a;
|
||||
|
||||
--quote-bg: hsl(60, 5%, 75%);
|
||||
--quote-border: hsl(60, 5%, 70%);
|
||||
|
||||
--table-border-color: hsl(60, 9%, 82%);
|
||||
--table-header-bg: #b3a497;
|
||||
--table-alternate-bg: hsl(60, 9%, 84%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #dec2a2;
|
||||
--search-mark-bg: #e69f67;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.light.no-js {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
}
|
||||
}
|
||||
|
||||
.colibri {
|
||||
--bg: #3b224c;
|
||||
--fg: #bcbdd0;
|
||||
--heading-fg: #fff;
|
||||
|
||||
--sidebar-bg: #281733;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505274;
|
||||
--sidebar-active: #a4a0e8;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
/* --links: #a4a0e8; */
|
||||
--links: #ECCDBA;
|
||||
|
||||
--inline-code-color: hsl(48.7, 7.8%, 70%);
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
--theme-hover: rgba(0,0,0, .2);
|
||||
|
||||
--quote-bg: #281733;
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(226, 23%, 76%);
|
||||
--table-header-bg: hsla(226, 23%, 31%, 0);
|
||||
--table-alternate-bg: hsl(226, 23%, 14%);
|
||||
--table-border-line: hsla(201deg, 20%, 92%, 0.2);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #aeaec6;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #5f5f71;
|
||||
--searchresults-border-color: #5c5c68;
|
||||
--searchresults-li-bg: #242430;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.colibri {
|
||||
/*
|
||||
--bg: #ffffff;
|
||||
--fg: #452859;
|
||||
--fg: #5a5977;
|
||||
--heading-fg: #281733;
|
||||
|
||||
--sidebar-bg: #281733;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505274;
|
||||
--sidebar-active: #a4a0e8;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #6F44F0;
|
||||
|
||||
--inline-code-color: #a39e9b;
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
--theme-hover: rgba(0,0,0, .2);
|
||||
|
||||
--quote-bg: rgba(0, 0, 0, 0);
|
||||
--quote-border: hsl(226, 15%, 75%);
|
||||
|
||||
--table-border-color: #5a5977;
|
||||
--table-border-color: hsl(201deg 10% 67%);
|
||||
--table-header-bg: hsl(0, 0%, 100%);
|
||||
--table-alternate-bg: hsl(0, 0%, 97%);
|
||||
--table-border-line: hsl(201deg, 20%, 92%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #aeaec6;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #5f5f71;
|
||||
--searchresults-border-color: #5c5c68;
|
||||
--searchresults-li-bg: #242430;
|
||||
--search-mark-bg: #a2cff5;
|
||||
*/
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -1 +1,22 @@
|
||||
<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>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 199.7 184.2">
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
svg { fill: white; }
|
||||
}
|
||||
</style>
|
||||
<path d="M189.5,36.8c0.2,2.8,0,5.1-0.6,6.8L153,162c-0.6,2.1-2,3.7-4.2,5c-2.2,1.2-4.4,1.9-6.7,1.9H31.4c-9.6,0-15.3-2.8-17.3-8.4
|
||||
c-0.8-2.2-0.8-3.9,0.1-5.2c0.9-1.2,2.4-1.8,4.6-1.8H123c7.4,0,12.6-1.4,15.4-4.1s5.7-8.9,8.6-18.4l32.9-108.6
|
||||
c1.8-5.9,1-11.1-2.2-15.6S169.9,0,164,0H72.7c-1,0-3.1,0.4-6.1,1.1l0.1-0.4C64.5,0.2,62.6,0,61,0.1s-3,0.5-4.3,1.4
|
||||
c-1.3,0.9-2.4,1.8-3.2,2.8S52,6.5,51.2,8.1c-0.8,1.6-1.4,3-1.9,4.3s-1.1,2.7-1.8,4.2c-0.7,1.5-1.3,2.7-2,3.7c-0.5,0.6-1.2,1.5-2,2.5
|
||||
s-1.6,2-2.2,2.8s-0.9,1.5-1.1,2.2c-0.2,0.7-0.1,1.8,0.2,3.2c0.3,1.4,0.4,2.4,0.4,3.1c-0.3,3-1.4,6.9-3.3,11.6
|
||||
c-1.9,4.7-3.6,8.1-5.1,10.1c-0.3,0.4-1.2,1.3-2.6,2.7c-1.4,1.4-2.3,2.6-2.6,3.7c-0.3,0.4-0.3,1.5-0.1,3.4c0.3,1.8,0.4,3.1,0.3,3.8
|
||||
c-0.3,2.7-1.3,6.3-3,10.8c-1.7,4.5-3.4,8.2-5,11c-0.2,0.5-0.9,1.4-2,2.8c-1.1,1.4-1.8,2.5-2,3.4c-0.2,0.6-0.1,1.8,0.1,3.4
|
||||
c0.2,1.6,0.2,2.8-0.1,3.6c-0.6,3-1.8,6.7-3.6,11c-1.8,4.3-3.6,7.9-5.4,11c-0.5,0.8-1.1,1.7-2,2.8c-0.8,1.1-1.5,2-2,2.8
|
||||
s-0.8,1.6-1,2.5c-0.1,0.5,0,1.3,0.4,2.3c0.3,1.1,0.4,1.9,0.4,2.6c-0.1,1.1-0.2,2.6-0.5,4.4c-0.2,1.8-0.4,2.9-0.4,3.2
|
||||
c-1.8,4.8-1.7,9.9,0.2,15.2c2.2,6.2,6.2,11.5,11.9,15.8c5.7,4.3,11.7,6.4,17.8,6.4h110.7c5.2,0,10.1-1.7,14.7-5.2s7.7-7.8,9.2-12.9
|
||||
l33-108.6c1.8-5.8,1-10.9-2.2-15.5C194.9,39.7,192.6,38,189.5,36.8z M59.6,122.8L73.8,80c0,0,7,0,10.8,0s28.8-1.7,25.4,17.5
|
||||
c-3.4,19.2-18.8,25.2-36.8,25.4S59.6,122.8,59.6,122.8z M78.6,116.8c4.7-0.1,18.9-2.9,22.1-17.1S89.2,86.3,89.2,86.3l-8.9,0
|
||||
l-10.2,30.5C70.2,116.9,74,116.9,78.6,116.8z M75.3,68.7L89,26.2h9.8l0.8,34l23.6-34h9.9l-13.6,42.5h-7.1l12.5-35.4l-24.5,35.4h-6.8
|
||||
l-0.8-35L82,68.7H75.3z"/>
|
||||
</svg>
|
||||
<!-- Original image Copyright Dave Gandy — CC BY 4.0 License -->
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.8 KiB |
56
book/theme/highlight.css
Normal file
56
book/theme/highlight.css
Normal file
@@ -0,0 +1,56 @@
|
||||
pre code.hljs {
|
||||
display:block;
|
||||
overflow-x:auto;
|
||||
padding:1em
|
||||
}
|
||||
code.hljs {
|
||||
padding:3px 5px
|
||||
}
|
||||
.hljs {
|
||||
background:#2f1e2e;
|
||||
color:#a39e9b
|
||||
}
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color:#8d8687
|
||||
}
|
||||
.hljs-link,
|
||||
.hljs-meta,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id,
|
||||
.hljs-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-variable {
|
||||
color:#ef6155
|
||||
}
|
||||
.hljs-built_in,
|
||||
.hljs-deletion,
|
||||
.hljs-literal,
|
||||
.hljs-number,
|
||||
.hljs-params,
|
||||
.hljs-type {
|
||||
color:#f99b15
|
||||
}
|
||||
.hljs-attribute,
|
||||
.hljs-section,
|
||||
.hljs-title {
|
||||
color:#fec418
|
||||
}
|
||||
.hljs-addition,
|
||||
.hljs-bullet,
|
||||
.hljs-string,
|
||||
.hljs-symbol {
|
||||
color:#48b685
|
||||
}
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color:#815ba4
|
||||
}
|
||||
.hljs-emphasis {
|
||||
font-style:italic
|
||||
}
|
||||
.hljs-strong {
|
||||
font-weight:700
|
||||
}
|
6
book/theme/highlight.js
Normal file
6
book/theme/highlight.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="{{ language }}" class="{{ default_theme }} sidebar-visible" dir="{{ text_direction }}">
|
||||
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
{{#if is_print }}
|
||||
<meta name="robots" content="noindex">
|
||||
<meta name="robots" content="noindex" />
|
||||
{{/if}}
|
||||
{{#if base_url}}
|
||||
<base href="{{ base_url }}">
|
||||
@@ -15,9 +15,10 @@
|
||||
<!-- Custom HTML head -->
|
||||
{{> head}}
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
{{#if favicon_svg}}
|
||||
<link rel="icon" href="{{ path_to_root }}favicon.svg">
|
||||
@@ -34,6 +35,8 @@
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;400;500;700&display=swap" rel="stylesheet">
|
||||
{{#if copy_fonts}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
|
||||
{{/if}}
|
||||
@@ -50,21 +53,18 @@
|
||||
|
||||
{{#if mathjax_support}}
|
||||
<!-- MathJax -->
|
||||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
{{/if}}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "{{ path_to_root }}";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="{{ path_to_root }}toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
@@ -80,42 +80,34 @@
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('{{ default_theme }}')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
html.classList.add('js');
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="{{ path_to_root }}toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
<div class="sidebar-scrollbox">
|
||||
{{#toc}}{{/toc}}
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
@@ -123,21 +115,21 @@
|
||||
<div class="page">
|
||||
{{> header}}
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div id="menu-bar" class="menu-bar sticky bordered">
|
||||
<div class="left-buttons">
|
||||
<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">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
</button>
|
||||
<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">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="colibri">Colibri</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="colibri">{{ theme_option "Colibri" }}</button></li>
|
||||
</ul>
|
||||
{{#if search_enabled}}
|
||||
<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">
|
||||
@@ -159,19 +151,13 @@
|
||||
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if git_repository_edit_url}}
|
||||
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
|
||||
<i id="git-edit-button" class="fa fa-edit"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if search_enabled}}
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
@@ -182,7 +168,7 @@
|
||||
{{/if}}
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
@@ -204,7 +190,7 @@
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
@@ -222,7 +208,7 @@
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
@@ -230,12 +216,10 @@
|
||||
|
||||
</div>
|
||||
|
||||
{{#if live_reload_endpoint}}
|
||||
{{#if livereload}}
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("{{{livereload}}}");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
@@ -251,7 +235,7 @@
|
||||
|
||||
{{#if google_analytics}}
|
||||
<!-- Google Analytics Tag -->
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
@@ -269,43 +253,43 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_line_numbers}}
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
window.playground_line_numbers = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_copyable}}
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_js}}
|
||||
<script src="{{ path_to_root }}ace.js"></script>
|
||||
<script src="{{ path_to_root }}editor.js"></script>
|
||||
<script src="{{ path_to_root }}mode-rust.js"></script>
|
||||
<script src="{{ path_to_root }}theme-dawn.js"></script>
|
||||
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
|
||||
<script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
{{/if}}
|
||||
|
||||
{{#if search_js}}
|
||||
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
|
||||
<script src="{{ path_to_root }}mark.min.js"></script>
|
||||
<script src="{{ path_to_root }}searcher.js"></script>
|
||||
<script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
{{/if}}
|
||||
|
||||
<script src="{{ path_to_root }}clipboard.min.js"></script>
|
||||
<script src="{{ path_to_root }}highlight.js"></script>
|
||||
<script src="{{ path_to_root }}book.js"></script>
|
||||
<script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
{{#each additional_js}}
|
||||
<script src="{{ ../path_to_root }}{{this}}"></script>
|
||||
<script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
|
||||
{{/each}}
|
||||
|
||||
{{#if is_print}}
|
||||
{{#if mathjax_support}}
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function() {
|
||||
MathJax.Hub.Register.StartupHook('End', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
@@ -313,7 +297,7 @@
|
||||
});
|
||||
</script>
|
||||
{{else}}
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
@@ -321,6 +305,5 @@
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,125 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.helix_editor.Helix</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>MPL-2.0</project_license>
|
||||
<name>Helix</name>
|
||||
<summary>A post-modern text editor</summary>
|
||||
<summary xml:lang="ar">مُحَرِّرُ نُصُوصٍ سَابِقٌ لِعَهدِه</summary>
|
||||
<developer id="com.helix_editor">
|
||||
<name>Blaž Hrastnik</name>
|
||||
</developer>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
Helix is a terminal-based text editor inspired by Kakoune / Neovim and written in Rust.
|
||||
</p>
|
||||
<p xml:lang="ar">
|
||||
مُحَرِّرُ نُصُوصٍ يَعمَلُ فِي الطَّرَفِيَّة، مُستَلهَمٌ مِن Kakoune وَ Neovim وَمَكتُوبٌ بِلُغَةِ رَست البَرمَجِيَّة.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Vim-like modal editing</li>
|
||||
<li xml:lang="ar">تَحرِيرٌ وَضعِيٌّ شَبيهٌ بِـVim</li>
|
||||
<li>Multiple selections</li>
|
||||
<li xml:lang="ar">تَحدِيدَاتٌ لِلنَّصِ مُتَعَدِّدَة</li>
|
||||
<li>Built-in language server support</li>
|
||||
<li xml:lang="ar">دَعْمٌ مُدمَجٌ لِخَوادِمِ اللُّغَات</li>
|
||||
<li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
|
||||
<li xml:lang="ar">تَحرِيرُ التَّعلِيمَاتِ البَّرمَجِيَّةِ مَعَ تَمييزٍ لِلتَّركِيبِ النَّحُويِّ بِواسِطَةِ tree-sitter</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">Helix.desktop</launchable>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Helix with default theme</caption>
|
||||
<image>https://github.com/helix-editor/helix/raw/d4565b4404cabc522bd60822abd374755581d751/screenshot.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<url type="homepage">https://helix-editor.com/</url>
|
||||
<url type="donation">https://opencollective.com/helix-editor</url>
|
||||
<url type="help">https://docs.helix-editor.com/</url>
|
||||
<url type="vcs-browser">https://github.com/helix-editor/helix</url>
|
||||
<url type="bugtracker">https://github.com/helix-editor/helix/issues</url>
|
||||
|
||||
<content_rating type="oars-1.1" />
|
||||
|
||||
<releases>
|
||||
<release version="25.07.1" date="2025-07-18">
|
||||
<url>https://github.com/helix-editor/helix/releases/tag/25.07.1</url>
|
||||
</release>
|
||||
<release version="25.07" date="2025-07-15">
|
||||
<url>https://helix-editor.com/news/release-25-07-highlights/</url>
|
||||
</release>
|
||||
<release version="25.01.1" date="2025-01-19">
|
||||
<url>https://github.com/helix-editor/helix/releases/tag/25.01.1</url>
|
||||
</release>
|
||||
<release version="25.01" date="2025-01-03">
|
||||
<url>https://helix-editor.com/news/release-25-01-highlights/</url>
|
||||
</release>
|
||||
<release version="24.07" date="2024-07-14">
|
||||
<url>https://github.com/helix-editor/helix/releases/tag/24.07</url>
|
||||
</release>
|
||||
<release version="24.03" date="2024-03-30">
|
||||
<url>https://helix-editor.com/news/release-24-03-highlights/</url>
|
||||
</release>
|
||||
<release version="23.10" date="2023-10-24">
|
||||
<url>https://helix-editor.com/news/release-23-10-highlights/</url>
|
||||
</release>
|
||||
<release version="23.05" date="2023-05-18">
|
||||
<url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
|
||||
</release>
|
||||
<release version="23.03" date="2023-03-31">
|
||||
<url>https://helix-editor.com/news/release-23-03-highlights/</url>
|
||||
</release>
|
||||
<release version="22.12" date="2022-12-6">
|
||||
<url>https://helix-editor.com/news/release-22-12-highlights/</url>
|
||||
</release>
|
||||
<release version="22.08" date="2022-8-31">
|
||||
<url>https://helix-editor.com/news/release-22-08-highlights/</url>
|
||||
</release>
|
||||
<release version="22.05" date="2022-5-28">
|
||||
<url>https://helix-editor.com/news/release-22-05-highlights/</url>
|
||||
</release>
|
||||
<release version="22.03" date="2022-3-28">
|
||||
<url>https://helix-editor.com/news/release-22-03-highlights/</url>
|
||||
</release>
|
||||
</releases>
|
||||
|
||||
<recommends>
|
||||
<control>keyboard</control>
|
||||
</recommends>
|
||||
|
||||
<categories>
|
||||
<category>Utility</category>
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
|
||||
<keywords>
|
||||
<keyword>text</keyword>
|
||||
<keyword>editor</keyword>
|
||||
<keyword>development</keyword>
|
||||
<keyword>programming</keyword>
|
||||
</keywords>
|
||||
|
||||
<provides>
|
||||
<binary>hx</binary>
|
||||
<mediatype>text/english</mediatype>
|
||||
<mediatype>text/plain</mediatype>
|
||||
<mediatype>text/x-makefile</mediatype>
|
||||
<mediatype>text/x-c++hdr</mediatype>
|
||||
<mediatype>text/x-c++src</mediatype>
|
||||
<mediatype>text/x-chdr</mediatype>
|
||||
<mediatype>text/x-csrc</mediatype>
|
||||
<mediatype>text/x-java</mediatype>
|
||||
<mediatype>text/x-moc</mediatype>
|
||||
<mediatype>text/x-pascal</mediatype>
|
||||
<mediatype>text/x-tcl</mediatype>
|
||||
<mediatype>text/x-tex</mediatype>
|
||||
<mediatype>application/x-shellscript</mediatype>
|
||||
<mediatype>text/x-c</mediatype>
|
||||
<mediatype>text/x-c++</mediatype>
|
||||
</provides>
|
||||
</component>
|
@@ -1,91 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=Helix
|
||||
GenericName=Text Editor
|
||||
GenericName[ar]=مُحَرِّرُ نُصُوص
|
||||
GenericName[de]=Texteditor
|
||||
GenericName[fr]=Éditeur de texte
|
||||
GenericName[ru]=Текстовый редактор
|
||||
GenericName[sr]=Едитор текст
|
||||
GenericName[tr]=Metin Düzenleyici
|
||||
Comment=Edit text files
|
||||
Comment[af]=Redigeer tekslêers
|
||||
Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
|
||||
Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
|
||||
Comment[az]=Mətn fayllarını redaktə edin
|
||||
Comment[be]=Рэдагаваньне тэкставых файлаў
|
||||
Comment[bg]=Редактиране на текстови файлове
|
||||
Comment[bn]=টেক্স্ট ফাইল এডিট করুন
|
||||
Comment[bs]=Izmijeni tekstualne datoteke
|
||||
Comment[ca]=Edita fitxers de text
|
||||
Comment[cs]=Úprava textových souborů
|
||||
Comment[cy]=Golygu ffeiliau testun
|
||||
Comment[da]=Redigér tekstfiler
|
||||
Comment[de]=Textdateien bearbeiten
|
||||
Comment[el]=Επεξεργασία αρχείων κειμένου
|
||||
Comment[en_CA]=Edit text files
|
||||
Comment[en_GB]=Edit text files
|
||||
Comment[es]=Edita archivos de texto
|
||||
Comment[et]=Redigeeri tekstifaile
|
||||
Comment[eu]=Editatu testu-fitxategiak
|
||||
Comment[fa]=ویرایش پروندههای متنی
|
||||
Comment[fi]=Muokkaa tekstitiedostoja
|
||||
Comment[fr]=Éditer des fichiers texte
|
||||
Comment[ga]=Eagar comhad Téacs
|
||||
Comment[gu]=લખાણ ફાઇલોમાં ફેરફાર કરો
|
||||
Comment[he]=ערוך קבצי טקסט
|
||||
Comment[hi]=पाठ फ़ाइलें संपादित करें
|
||||
Comment[hr]=Uređivanje tekstualne datoteke
|
||||
Comment[hu]=Szövegfájlok szerkesztése
|
||||
Comment[id]=Edit file teks
|
||||
Comment[it]=Modifica file di testo
|
||||
Comment[ja]=テキストファイルを編集します
|
||||
Comment[kn]=ಪಠ್ಯ ಕಡತಗಳನ್ನು ಸಂಪಾದಿಸು
|
||||
Comment[ko]=텍스트 파일을 편집합니다
|
||||
Comment[lt]=Redaguoti tekstines bylas
|
||||
Comment[lv]=Rediģēt teksta failus
|
||||
Comment[mk]=Уреди текстуални фајлови
|
||||
Comment[ml]=വാചക രചനകള് തിരുത്തുക
|
||||
Comment[mn]=Текст файл боловсруулах
|
||||
Comment[mr]=गद्य फाइल संपादित करा
|
||||
Comment[ms]=Edit fail teks
|
||||
Comment[nb]=Rediger tekstfiler
|
||||
Comment[ne]=पाठ फाइललाई संशोधन गर्नुहोस्
|
||||
Comment[nl]=Tekstbestanden bewerken
|
||||
Comment[nn]=Rediger tekstfiler
|
||||
Comment[no]=Rediger tekstfiler
|
||||
Comment[or]=ପାଠ୍ଯ ଫାଇଲଗୁଡ଼ିକୁ ସମ୍ପାଦନ କରନ୍ତୁ
|
||||
Comment[pa]=ਪਾਠ ਫਾਇਲਾਂ ਸੰਪਾਦਨ
|
||||
Comment[pl]=Edytor plików tekstowych
|
||||
Comment[pt]=Editar ficheiros de texto
|
||||
Comment[pt_BR]=Edite arquivos de texto
|
||||
Comment[ro]=Editare fişiere text
|
||||
Comment[ru]=Редактирование текстовых файлов
|
||||
Comment[sk]=Úprava textových súborov
|
||||
Comment[sl]=Urejanje datotek z besedili
|
||||
Comment[sq]=Përpuno files teksti
|
||||
Comment[sr]=Уређујте текст фајлове
|
||||
Comment[sr@Latn]=Izmeni tekstualne datoteke
|
||||
Comment[sv]=Redigera textfiler
|
||||
Comment[ta]=உரை கோப்புகளை தொகுக்கவும்
|
||||
Comment[th]=แก้ไขแฟ้มข้อความ
|
||||
Comment[tk]=Metin faýllary editle
|
||||
Comment[tr]=Metin dosyaları düzenleyin
|
||||
Comment[uk]=Редактор текстових файлів
|
||||
Comment[vi]=Soạn thảo tập tin văn bản
|
||||
Comment[wa]=Asspougnî des fitchîs tecses
|
||||
Comment[zh_CN]=编辑文本文件
|
||||
Comment[zh_TW]=編輯文字檔
|
||||
TryExec=hx
|
||||
Exec=hx %F
|
||||
Terminal=true
|
||||
Type=Application
|
||||
Keywords=Text;editor;
|
||||
Keywords[ar]=نص;نصوص;محرر;
|
||||
Keywords[fr]=Texte;éditeur;
|
||||
Keywords[ru]=текст;текстовый редактор;
|
||||
Keywords[sr]=Текст;едитор;
|
||||
Keywords[tr]=Metin;düzenleyici;
|
||||
Icon=helix
|
||||
Categories=Utility;TextEditor;ConsoleOnly
|
||||
StartupNotify=false
|
||||
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++;
|
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bash completion script for Helix editor
|
||||
|
||||
_hx() {
|
||||
local cur prev languages
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD - 1]}"
|
||||
|
||||
case "$prev" in
|
||||
-g | --grammar)
|
||||
mapfile -t COMPREPLY < <(compgen -W 'fetch build' -- "$cur")
|
||||
return 0
|
||||
;;
|
||||
--health)
|
||||
languages=$(hx --health all-languages | tail -n '+2' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g')
|
||||
mapfile -t COMPREPLY < <(compgen -W """clipboard languages all-languages all $languages""" -- "$cur")
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$2" in
|
||||
-*)
|
||||
mapfile -t COMPREPLY < <(compgen -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2""")
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
mapfile -t COMPREPLY < <(compgen -fd -- """$2""")
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
} && complete -o filenames -F _hx hx
|
@@ -1,55 +0,0 @@
|
||||
# You can move it here ~/.config/elvish/lib/hx.elv
|
||||
# Or add `eval (slurp < ~/$REPOS/helix/contrib/completion/hx.elv)`
|
||||
# Be sure to replace `$REPOS` with something that makes sense for you!
|
||||
|
||||
### Renders a pretty completion candidate
|
||||
var candidate = { | _stem _desc |
|
||||
edit:complex-candidate $_stem &display=(styled $_stem bold)(styled " "$_desc dim)
|
||||
}
|
||||
|
||||
### These commands will invalidate further input (i.e. not react to them)
|
||||
var skips = [ "--tutor" "--help" "--version" "-V" "--health" ]
|
||||
|
||||
### Grammar commands
|
||||
var grammar = [ "--grammar" "-g" ]
|
||||
|
||||
### Config commands
|
||||
var config = [ "--config" "-c" ]
|
||||
|
||||
### Set an arg-completer for the `hx` binary
|
||||
set edit:completion:arg-completer[hx] = {|@args|
|
||||
var n = (count $args)
|
||||
if (>= $n 3) {
|
||||
# Stop completions if passed arg will take precedence
|
||||
# and invalidate further input
|
||||
if (has-value $skips $args[-2]) {
|
||||
return
|
||||
}
|
||||
# If the previous arg == --grammar, then only suggest:
|
||||
if (has-value $grammar $args[-2]) {
|
||||
$candidate "fetch" "Fetch the tree-sitter grammars"
|
||||
$candidate "build" "Build the tree-sitter grammars"
|
||||
return
|
||||
}
|
||||
# When we have --config, we need a file
|
||||
if (has-values $config $args[-2]) {
|
||||
edit:complete-filename $args[-1] | each { |v| put $v[stem] }
|
||||
return
|
||||
}
|
||||
# When we have --log, we need a file
|
||||
if (has-values "log" $args[-2]) {
|
||||
edit:complete-filename $args[-1] | each { |v| put $v[stem] }
|
||||
return
|
||||
}
|
||||
}
|
||||
edit:complete-filename $args[-1] | each { |v| put $v[stem]}
|
||||
$candidate "--help" "(Prints help information)"
|
||||
$candidate "--version" "(Prints version information)"
|
||||
$candidate "--tutor" "(Loads the tutorial)"
|
||||
$candidate "--health" "(Checks for errors in editor setup)"
|
||||
$candidate "--grammar" "(Fetch or build the tree-sitter grammars)"
|
||||
$candidate "--vsplit" "(Splits all given files vertically)"
|
||||
$candidate "--hsplit" "(Splits all given files horizontally)"
|
||||
$candidate "--config" "(Specifies a file to use for configuration)"
|
||||
$candidate "--log" "(Specifies a file to write log data into)"
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
# Fish completion script for Helix editor
|
||||
|
||||
complete -c hx -s h -l help -d "Prints help information"
|
||||
complete -c hx -l tutor -d "Loads the tutorial"
|
||||
complete -c hx -l health -xa "(__hx_langs_ops)" -d "Checks for errors"
|
||||
complete -c hx -l health -xka all -d "Prints all diagnostic informations"
|
||||
complete -c hx -l health -xka all-languages -d "Lists all languages"
|
||||
complete -c hx -l health -xka languages -d "Lists user configured languages"
|
||||
complete -c hx -l health -xka clipboard -d "Prints system clipboard provider"
|
||||
complete -c hx -s g -l grammar -x -a "fetch build" -d "Fetch or build tree-sitter grammars"
|
||||
complete -c hx -s v -o vv -o vvv -d "Increases logging verbosity"
|
||||
complete -c hx -s V -l version -d "Prints version information"
|
||||
complete -c hx -l vsplit -d "Splits all given files vertically"
|
||||
complete -c hx -l hsplit -d "Splits all given files horizontally"
|
||||
complete -c hx -s c -l config -r -d "Specifies a file to use for config"
|
||||
complete -c hx -l log -r -d "Specifies a file to use for logging"
|
||||
complete -c hx -s w -l working-dir -d "Specify initial working directory" -xa "(__fish_complete_directories)"
|
||||
|
||||
function __hx_langs_ops
|
||||
hx --health all-languages | tail -n '+2' | string replace -fr '^(\S+) .*' '$1'
|
||||
end
|
@@ -1,29 +0,0 @@
|
||||
# Completions for Helix: <https://github.com/helix-editor/helix>
|
||||
#
|
||||
# NOTE: the `+N` syntax is not supported in Nushell (https://github.com/nushell/nushell/issues/13418)
|
||||
# so it has not been specified here and will not be proposed in the autocompletion of Nushell.
|
||||
# The help message won't be overridden though, so it will still be present here
|
||||
|
||||
def health_categories [] {
|
||||
let languages = ^hx --health all-languages | detect columns | get Language | where { $in != null }
|
||||
let completions = [ "all", "clipboard", "languages", "all-languages" ] | append $languages
|
||||
return $completions
|
||||
}
|
||||
|
||||
def grammar_categories [] { ["fetch", "build"] }
|
||||
|
||||
# A post-modern text editor.
|
||||
export extern hx [
|
||||
--help(-h), # Prints help information
|
||||
--tutor, # Loads the tutorial
|
||||
--health: string@health_categories, # Checks for potential errors in editor setup
|
||||
--grammar(-g): string@grammar_categories, # Fetches or builds tree-sitter grammars listed in `languages.toml`
|
||||
--config(-c): glob, # Specifies a file to use for configuration
|
||||
-v, # Increases logging verbosity each use for up to 3 times
|
||||
--log: glob, # Specifies a file to use for logging
|
||||
--version(-V), # Prints version information
|
||||
--vsplit, # Splits all given files vertically into different windows
|
||||
--hsplit, # Splits all given files horizontally into different windows
|
||||
--working-dir(-w): glob, # Specify an initial working directory
|
||||
...files: glob, # Sets the input file to use, position can also be specified via file[:row[:col]]
|
||||
]
|
@@ -1,35 +0,0 @@
|
||||
#compdef _hx hx
|
||||
# Zsh completion script for Helix editor
|
||||
|
||||
_hx() {
|
||||
_arguments -C \
|
||||
"-h[Prints help information]" \
|
||||
"--help[Prints help information]" \
|
||||
"-v[Increase logging verbosity]" \
|
||||
"-vv[Increase logging verbosity]" \
|
||||
"-vvv[Increase logging verbosity]" \
|
||||
"-V[Prints version information]" \
|
||||
"--version[Prints version information]" \
|
||||
"--tutor[Loads the tutorial]" \
|
||||
"--health[Checks for errors in editor setup]:language:->health" \
|
||||
"-g[Fetches or builds tree-sitter grammars]:action:->grammar" \
|
||||
"--grammar[Fetches or builds tree-sitter grammars]:action:->grammar" \
|
||||
"--vsplit[Splits all given files vertically]" \
|
||||
"--hsplit[Splits all given files horizontally]" \
|
||||
"-c[Specifies a file to use for configuration]" \
|
||||
"--config[Specifies a file to use for configuration]" \
|
||||
"-w[Specify initial working directory]" \
|
||||
"--working-dir[Specify initial working directory]" \
|
||||
"--log[Specifies a file to use for logging]" \
|
||||
"*:file:_files"
|
||||
|
||||
case "$state" in
|
||||
health)
|
||||
local languages=($(hx --health all-languages | tail -n '+2' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g;s/[✘✓]//g'))
|
||||
_values 'language' $languages
|
||||
;;
|
||||
grammar)
|
||||
_values 'action' fetch build
|
||||
;;
|
||||
esac
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 264 KiB |
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
HELIX_RUNTIME=/usr/lib/helix/runtime exec /usr/lib/helix/hx "$@"
|
87
default.nix
87
default.nix
@@ -1,87 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
callPackage,
|
||||
runCommand,
|
||||
installShellFiles,
|
||||
git,
|
||||
gitRev ? null,
|
||||
grammarOverlays ? [],
|
||||
includeGrammarIf ? _: true,
|
||||
}: let
|
||||
fs = lib.fileset;
|
||||
|
||||
src = fs.difference (fs.gitTracked ./.) (fs.unions [
|
||||
./.envrc
|
||||
./rustfmt.toml
|
||||
./screenshot.png
|
||||
./book
|
||||
./docs
|
||||
./runtime
|
||||
./flake.lock
|
||||
(fs.fileFilter (file: lib.strings.hasInfix ".git" file.name) ./.)
|
||||
(fs.fileFilter (file: file.hasExt "svg") ./.)
|
||||
(fs.fileFilter (file: file.hasExt "md") ./.)
|
||||
(fs.fileFilter (file: file.hasExt "nix") ./.)
|
||||
]);
|
||||
|
||||
# Next we actually need to build the grammars and the runtime directory
|
||||
# that they reside in. It is built by calling the derivation in the
|
||||
# grammars.nix file, then taking the runtime directory in the git repo
|
||||
# and hooking symlinks up to it.
|
||||
grammars = callPackage ./grammars.nix {inherit grammarOverlays includeGrammarIf;};
|
||||
runtimeDir = runCommand "helix-runtime" {} ''
|
||||
mkdir -p $out
|
||||
ln -s ${./runtime}/* $out
|
||||
rm -r $out/grammars
|
||||
ln -s ${grammars} $out/grammars
|
||||
'';
|
||||
in
|
||||
rustPlatform.buildRustPackage (self: {
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
# This is not allowed in nixpkgs but is very convenient here: it allows us to
|
||||
# avoid specifying `outputHashes` here for any git dependencies we might take
|
||||
# on temporarily.
|
||||
allowBuiltinFetchGit = true;
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [ runtimeDir ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
installShellFiles
|
||||
git
|
||||
];
|
||||
|
||||
buildType = "release";
|
||||
|
||||
name = with builtins; (fromTOML (readFile ./helix-term/Cargo.toml)).package.name;
|
||||
src = fs.toSource {
|
||||
root = ./.;
|
||||
fileset = src;
|
||||
};
|
||||
|
||||
# Helix attempts to reach out to the network and get the grammars. Nix doesn't allow this.
|
||||
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
|
||||
|
||||
# So Helix knows what rev it is.
|
||||
HELIX_NIX_BUILD_REV = gitRev;
|
||||
|
||||
doCheck = false;
|
||||
strictDeps = true;
|
||||
|
||||
# Sets the Helix runtime dir to the grammars
|
||||
env.HELIX_DEFAULT_RUNTIME = "${runtimeDir}";
|
||||
|
||||
# Get all the application stuff in the output directory.
|
||||
postInstall = ''
|
||||
mkdir -p $out/lib
|
||||
installShellCompletion ${./contrib/completion}/hx.{bash,fish,zsh}
|
||||
mkdir -p $out/share/{applications,icons/hicolor/{256x256,scalable}/apps}
|
||||
cp ${./contrib/Helix.desktop} $out/share/applications/Helix.desktop
|
||||
cp ${./logo.svg} $out/share/icons/hicolor/scalable/apps/helix.svg
|
||||
cp ${./contrib/helix.png} $out/share/icons/hicolor/256x256/apps/helix.png
|
||||
'';
|
||||
|
||||
meta.mainProgram = "hx";
|
||||
})
|
@@ -8,15 +8,10 @@ Some suggestions to get started:
|
||||
- Help with packaging on various distributions needed!
|
||||
- To use print debugging to the [Helix log file][log-file], you must:
|
||||
* Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`)
|
||||
* Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher verbosity)
|
||||
* 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`
|
||||
- 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
|
||||
- Looking for even faster compile times? Give a try to [mold](https://github.com/rui314/mold)
|
||||
* Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher severity inclusive)
|
||||
- If your preferred language is missing, integrating a tree-sitter grammar for
|
||||
it and defining syntax highlight queries for it is straight forward and
|
||||
doesn't require much knowledge of the internals.
|
||||
- If you don't use the Nix development shell and are getting your rust-analyzer binary from rustup, you may need to run `rustup component add rust-analyzer`.
|
||||
This is because `rust-toolchain.toml` selects our MSRV for the development toolchain but doesn't download the matching rust-analyzer automatically.
|
||||
|
||||
We provide an [architecture.md][architecture.md] that should give you
|
||||
a good overview of the internals.
|
||||
@@ -31,50 +26,12 @@ files, run
|
||||
cargo xtask docgen
|
||||
```
|
||||
|
||||
inside the project. We use [xtask][xtask] as an ad-hoc task runner.
|
||||
inside the project. We use [xtask][xtask] as an ad-hoc task runner and
|
||||
thus do not require any dependencies other than `cargo` (You don't have
|
||||
to `cargo install` anything either).
|
||||
|
||||
To preview the book itself, install [mdbook][mdbook]. Then, run
|
||||
|
||||
```shell
|
||||
mdbook serve book
|
||||
```
|
||||
|
||||
and visit [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
# Testing
|
||||
|
||||
## Unit tests/Documentation tests
|
||||
|
||||
Run `cargo test --workspace` to run unit tests and documentation tests in all packages.
|
||||
|
||||
## Integration tests
|
||||
|
||||
Integration tests for helix-term can be run with `cargo integration-test`. Code
|
||||
contributors are strongly encouraged to write integration tests for their code.
|
||||
Existing tests can be used as examples. Helpers can be found in
|
||||
[helpers.rs][helpers.rs]. The log level can be set with the `HELIX_LOG_LEVEL`
|
||||
environment variable, e.g. `HELIX_LOG_LEVEL=debug cargo integration-test`.
|
||||
|
||||
Contributors using MacOS might encounter `Too many open files (os error 24)`
|
||||
failures while running integration tests. This can be resolved by increasing
|
||||
the default value (e.g. to `10240` from `256`) by running `ulimit -n 10240`.
|
||||
|
||||
## Minimum Stable Rust Version (MSRV) Policy
|
||||
|
||||
Helix keeps an intentionally low MSRV for the sake of easy building and packaging
|
||||
downstream. We follow [Firefox's MSRV policy]. Once Firefox's MSRV increases we
|
||||
may bump ours as well, but be sure to check that popular distributions like Ubuntu
|
||||
package the new MSRV version. When increasing the MSRV, update these three places:
|
||||
|
||||
* the `workspace.package.rust-version` key in `Cargo.toml` in the repository root
|
||||
* the `env.MSRV` key at the top of `.github/workflows/build.yml`
|
||||
* the `toolchain.channel` key in `rust-toolchain.toml`
|
||||
|
||||
[Firefox's MSRV policy]: https://firefox-source-docs.mozilla.org/writing-rust-code/update-policy.html
|
||||
[good-first-issue]: https://github.com/helix-editor/helix/labels/E-easy
|
||||
[log-file]: https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file
|
||||
[architecture.md]: ./architecture.md
|
||||
[docs]: https://docs.helix-editor.com/
|
||||
[xtask]: https://github.com/matklad/cargo-xtask
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
|
||||
[helpers.rs]: ../helix-term/tests/test/helpers.rs
|
||||
|
@@ -1,12 +1,9 @@
|
||||
|
||||
| Crate | Description |
|
||||
| ----------- | ----------- |
|
||||
| helix-stdx | Extensions to the standard library (similar to [`rust-analyzer`'s](https://github.com/rust-lang/rust-analyzer/blob/ea413f67a8f730b4211c09e103f8207c62e7dbc3/crates/stdx/Cargo.toml#L5)) |
|
||||
| helix-core | Core editing primitives, functional. |
|
||||
| helix-lsp | Language server client |
|
||||
| helix-lsp-types | Language Server Protocol type definitions |
|
||||
| helix-dap | Debug Adapter Protocol (DAP) client |
|
||||
| helix-event | Primitives for defining and handling events within the editor |
|
||||
| helix-loader | Functions for building, fetching, and loading external resources |
|
||||
| helix-view | UI abstractions for use in backends, imperative shell. |
|
||||
| helix-term | Terminal UI |
|
||||
@@ -35,7 +32,7 @@ represented by a `Selection`. Each `Range` in the selection consists of a moving
|
||||
a selection with a single range, with the head and the anchor in the same
|
||||
position.
|
||||
|
||||
Ropes are modified by constructing an OT-like `Transaction`. It represents
|
||||
Ropes are modified by constructing an OT-like `Transaction`. It's represents
|
||||
a single coherent change to the document and can be applied to the rope.
|
||||
A transaction can be inverted to produce an undo. Selections and marks can be
|
||||
mapped over a transaction to translate to a position in the new text state after
|
||||
@@ -45,7 +42,7 @@ applying the transaction.
|
||||
> interface used to generate text edits.
|
||||
|
||||
`Syntax` is the interface used to interact with tree-sitter ASTs for syntax
|
||||
highlighting and other features.
|
||||
highling and other features.
|
||||
|
||||
## View
|
||||
|
||||
@@ -112,17 +109,3 @@ The `main` function sets up a new `Application` that runs the event loop.
|
||||
## TUI / Term
|
||||
|
||||
TODO: document Component and rendering related stuff
|
||||
|
||||
## Event
|
||||
|
||||
The `helix-event` crate defines primitives for defining and acting on events
|
||||
within the editor. "Events" cover things like opening, changing and closing of
|
||||
documents, starting and stopping of language servers and more.
|
||||
|
||||
`helix-event` has tools for defining events and registering _hooks_ which run
|
||||
any time an event is emitted. `helix-event` also provides `AsyncHook` - a tool
|
||||
for running cancellable tasks which run after events with _debouncing_.
|
||||
|
||||
See the `AsyncHook` type for more information. Events can be created within the
|
||||
`events!` macro. Synchronous hooks can be created with `register_hook!`. And
|
||||
editor-wide events can be sent to hooks with `helix_event::dispatch`.
|
||||
|
@@ -1,66 +0,0 @@
|
||||
## Checklist
|
||||
|
||||
Helix releases are versioned in the Calendar Versioning scheme:
|
||||
`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
|
||||
`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
|
||||
being published.
|
||||
|
||||
* Merge the PR with the release updates. That branch should:
|
||||
* Update the version:
|
||||
* Update the `workspace.package.version` key in `Cargo.toml`. Cargo only accepts
|
||||
SemVer versions so a CalVer version of `22.07` for example must be formatted
|
||||
as `22.7.0`. Patch/bugfix releases should increment the SemVer patch number. A
|
||||
patch release for 22.07 would be `22.7.1`.
|
||||
* Run `cargo check` and commit the resulting change to `Cargo.lock`
|
||||
* Add changelog notes to `CHANGELOG.md`
|
||||
* 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)
|
||||
* Tag and push
|
||||
* Switch to master and pull
|
||||
* `git tag -s -m "<tag>" -a <tag> && git push origin <tag>` (note the `-s` which signs the tag)
|
||||
* Wait for the Release CI to finish
|
||||
* It will automatically turn the git tag into a GitHub release when it uploads artifacts
|
||||
* Edit the new release
|
||||
* Use `<tag>` as the title
|
||||
* Link to the changelog and release notes
|
||||
* Merge the release notes PR
|
||||
* Download the macos and linux binaries and update the `sha256`s in the [homebrew formula]
|
||||
* Use `sha256sum` on the downloaded `.tar.xz` files to determine the hash
|
||||
* Link to the release notes in this-week-in-rust
|
||||
* [Example PR](https://github.com/rust-lang/this-week-in-rust/pull/3300)
|
||||
* Post to reddit
|
||||
* [Example post](https://www.reddit.com/r/rust/comments/uzp5ze/helix_editor_2205_released/)
|
||||
|
||||
[homebrew formula]: https://github.com/Homebrew/homebrew-core/blob/master/Formula/h/helix.rb
|
||||
|
||||
## Changelog Curation
|
||||
|
||||
The changelog is currently created manually by reading through commits in the
|
||||
log since the last release. GitHub's compare view is a nice way to approach
|
||||
this. For example, when creating the 22.07 release notes, this compare link
|
||||
may be used
|
||||
|
||||
```
|
||||
https://github.com/helix-editor/helix/compare/22.05...master
|
||||
```
|
||||
|
||||
Either side of the triple-dot may be replaced with an exact revision, so if
|
||||
you wish to incrementally compile the changelog, you can tackle a weeks worth
|
||||
or so, record the revision where you stopped, and use that as a starting point
|
||||
next week:
|
||||
|
||||
```
|
||||
https://github.com/helix-editor/helix/compare/7706a4a0d8b67b943c31d0c5f7b00d357b5d838d...master
|
||||
```
|
||||
|
||||
A work-in-progress commit for a changelog might look like
|
||||
[this example](https://github.com/helix-editor/helix/commit/831adfd4c709ca16b248799bfef19698d5175e55).
|
||||
|
||||
Not every PR or commit needs a blurb in the changelog. Each release section
|
||||
tends to have a blurb that links to a GitHub comparison between release
|
||||
versions for convenience:
|
||||
|
||||
> As usual, the following is a summary of each of the changes since the last
|
||||
> release. For the full log, check out the git log.
|
||||
|
||||
Typically, small changes like dependencies or documentation updates, refactors,
|
||||
or meta changes like GitHub Actions work are left out.
|
@@ -20,5 +20,5 @@ Vision statements are all well and good, but are also vague and subjective. Her
|
||||
* **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.
|
||||
* **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.)
|
||||
* **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.
|
||||
* **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.
|
||||
* **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. Right now we're thinking Wasm-based plugins.
|
||||
* **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.
|
||||
|
158
flake.lock
generated
158
flake.lock
generated
@@ -1,12 +1,156 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1644785799,
|
||||
"narHash": "sha256-VpAJO1L0XeBvtCuNGK4IDKp6ENHIpTrlaZT7yfBCvwo=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "fc7a94f841347c88f2cb44217b2a3faa93e2a0b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1646667754,
|
||||
"narHash": "sha256-LahZHvCC3UVzGQ55iWDRZkuDssXl1rYgqgScrPV9S38=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "59fbe1dfc0de8c3332957c16998a7d16dff365d8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"dream2nix": {
|
||||
"inputs": {
|
||||
"alejandra": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
],
|
||||
"crane": "crane",
|
||||
"flake-utils-pre-commit": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
],
|
||||
"gomod2nix": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
],
|
||||
"mach-nix": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
],
|
||||
"node2nix": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
],
|
||||
"poetry2nix": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"nixCargoIntegration",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1646710334,
|
||||
"narHash": "sha256-eLBcDgcbOUfeH4k6SEW5a5v0PTp2KNCn+5ZXIoWGYww=",
|
||||
"owner": "nix-community",
|
||||
"repo": "dream2nix",
|
||||
"rev": "5dcfbfd3b60ce0208b894c1bdea00e2bdf80ca6a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "main",
|
||||
"repo": "dream2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1642700792,
|
||||
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1637014545,
|
||||
"narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixCargoIntegration": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"dream2nix": "dream2nix",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rustOverlay": [
|
||||
"rust-overlay"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1646766572,
|
||||
"narHash": "sha256-DV3+zxvAIKsMHsHedJKYFsracvFyLKpFQqurUBR86oY=",
|
||||
"owner": "yusdacra",
|
||||
"repo": "nix-cargo-integration",
|
||||
"rev": "3a3f47f43ba486b7554164a698c8dfc5a38624ce",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "yusdacra",
|
||||
"repo": "nix-cargo-integration",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1740560979,
|
||||
"narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=",
|
||||
"lastModified": 1646497237,
|
||||
"narHash": "sha256-Ccpot1h/rV8MgcngDp5OrdmLTMaUTbStZTR5/sI7zW0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5135c59491985879812717f4c9fea69604e7f26f",
|
||||
"rev": "062a0c5437b68f950b081bbfc8a699d57a4ee026",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,22 +162,24 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixCargoIntegration": "nixCargoIntegration",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1740623427,
|
||||
"narHash": "sha256-3SdPQrZoa4odlScFDUHd4CUPQ/R1gtH4Mq9u8CBiK8M=",
|
||||
"lastModified": 1646792695,
|
||||
"narHash": "sha256-2drCXIKIQnJMlTZbcCfuHZAh+iPcdlRkCqtZnA6MHLY=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "d342e8b5fd88421ff982f383c853f0fc78a847ab",
|
||||
"rev": "7f599870402c8d2a5806086c8ee0f2d92b175c54",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
127
flake.nix
127
flake.nix
@@ -7,96 +7,59 @@
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
nixCargoIntegration = {
|
||||
url = "github:yusdacra/nix-cargo-integration";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.rustOverlay.follows = "rust-overlay";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
rust-overlay,
|
||||
...
|
||||
}: let
|
||||
inherit (nixpkgs) lib;
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
eachSystem = lib.genAttrs systems;
|
||||
pkgsFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem.system = system;
|
||||
overlays = [(import rust-overlay) self.overlays.helix];
|
||||
});
|
||||
gitRev = self.rev or self.dirtyRev or null;
|
||||
in {
|
||||
packages = eachSystem (system: {
|
||||
inherit (pkgsFor.${system}) helix;
|
||||
/*
|
||||
The default Helix build. Uses the latest stable Rust toolchain, and unstable
|
||||
nixpkgs.
|
||||
|
||||
The build inputs can be overridden with the following:
|
||||
|
||||
packages.${system}.default.override { rustPlatform = newPlatform; };
|
||||
|
||||
Overriding a derivation attribute can be done as well:
|
||||
|
||||
packages.${system}.default.overrideAttrs { buildType = "debug"; };
|
||||
*/
|
||||
default = self.packages.${system}.helix;
|
||||
});
|
||||
checks =
|
||||
lib.mapAttrs (system: pkgs: let
|
||||
# Get Helix's MSRV toolchain to build with by default.
|
||||
msrvToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||
msrvPlatform = pkgs.makeRustPlatform {
|
||||
cargo = msrvToolchain;
|
||||
rustc = msrvToolchain;
|
||||
outputs = inputs@{ nixCargoIntegration, ... }:
|
||||
nixCargoIntegration.lib.makeOutputs {
|
||||
root = ./.;
|
||||
renameOutputs = { "helix-term" = "helix"; };
|
||||
# Set default app to hx (binary is from helix-term release build)
|
||||
# Set default package to helix-term release build
|
||||
defaultOutputs = {
|
||||
app = "hx";
|
||||
package = "helix";
|
||||
};
|
||||
in {
|
||||
helix = self.packages.${system}.helix.override {
|
||||
rustPlatform = msrvPlatform;
|
||||
};
|
||||
})
|
||||
pkgsFor;
|
||||
|
||||
# Devshell behavior is preserved.
|
||||
devShells =
|
||||
lib.mapAttrs (system: pkgs: {
|
||||
default = let
|
||||
commonRustFlagsEnv = "-C link-arg=-fuse-ld=lld -C target-cpu=native --cfg tokio_unstable";
|
||||
platformRustFlagsEnv = lib.optionalString pkgs.stdenv.isLinux "-Clink-arg=-Wl,--no-rosegment";
|
||||
overrides = {
|
||||
crateOverrides = common: _: {
|
||||
helix-term = prev:
|
||||
let
|
||||
inherit (common) pkgs;
|
||||
grammars = pkgs.callPackage ./grammars.nix { };
|
||||
runtimeDir = pkgs.runCommand "helix-runtime" { } ''
|
||||
mkdir -p $out
|
||||
ln -s ${common.root}/runtime/* $out
|
||||
rm -r $out/grammars
|
||||
ln -s ${grammars} $out/grammars
|
||||
'';
|
||||
in
|
||||
pkgs.mkShell {
|
||||
inputsFrom = [self.checks.${system}.helix];
|
||||
nativeBuildInputs = with pkgs;
|
||||
[
|
||||
lld
|
||||
cargo-flamegraph
|
||||
rust-bin.nightly.latest.rust-analyzer
|
||||
]
|
||||
++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin)
|
||||
++ (lib.optional stdenv.isLinux lldb)
|
||||
++ (lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.CoreFoundation);
|
||||
shellHook = ''
|
||||
export RUST_BACKTRACE="1"
|
||||
export RUSTFLAGS="''${RUSTFLAGS:-""} ${commonRustFlagsEnv} ${platformRustFlagsEnv}"
|
||||
{
|
||||
# disable fetching and building of tree-sitter grammars in the helix-term build.rs
|
||||
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
|
||||
# link languages and theme toml files since helix-term expects them (for tests)
|
||||
preConfigure = "ln -s ${common.root}/{languages.toml,theme.toml,base16_theme.toml} ..";
|
||||
buildInputs = (prev.buildInputs or [ ]) ++ [ common.cCompiler.cc.lib ];
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
|
||||
postFixup = ''
|
||||
if [ -f "$out/bin/hx" ]; then
|
||||
wrapProgram "$out/bin/hx" --set HELIX_RUNTIME "${runtimeDir}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
})
|
||||
pkgsFor;
|
||||
|
||||
overlays = {
|
||||
helix = final: prev: {
|
||||
helix = final.callPackage ./default.nix {inherit gitRev;};
|
||||
};
|
||||
|
||||
default = self.overlays.helix;
|
||||
shell = common: prev: {
|
||||
packages = prev.packages ++ (with common.pkgs; [ lld_13 lldb cargo-tarpaulin cargo-flamegraph ]);
|
||||
env = prev.env ++ [
|
||||
{ name = "HELIX_RUNTIME"; eval = "$PWD/runtime"; }
|
||||
{ name = "RUST_BACKTRACE"; value = "1"; }
|
||||
{ name = "RUSTFLAGS"; value = "-C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment"; }
|
||||
];
|
||||
};
|
||||
};
|
||||
nixConfig = {
|
||||
extra-substituters = ["https://helix.cachix.org"];
|
||||
extra-trusted-public-keys = ["helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs="];
|
||||
};
|
||||
}
|
||||
|
110
grammars.nix
110
grammars.nix
@@ -1,71 +1,41 @@
|
||||
{
|
||||
stdenv,
|
||||
lib,
|
||||
runCommand,
|
||||
includeGrammarIf ? _: true,
|
||||
grammarOverlays ? [],
|
||||
...
|
||||
}: let
|
||||
{ stdenv, lib, runCommand, yj }:
|
||||
let
|
||||
# HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
|
||||
# before parsing
|
||||
languages-json = runCommand "languages-toml-to-json" { } ''
|
||||
${yj}/bin/yj -t < ${./languages.toml} > $out
|
||||
'';
|
||||
languagesConfig =
|
||||
builtins.fromTOML (builtins.readFile ./languages.toml);
|
||||
isGitGrammar = grammar:
|
||||
builtins.hasAttr "source" grammar
|
||||
&& builtins.hasAttr "git" grammar.source
|
||||
&& builtins.hasAttr "rev" grammar.source;
|
||||
isGitHubGrammar = grammar: lib.hasPrefix "https://github.com" grammar.source.git;
|
||||
toGitHubFetcher = url: let
|
||||
match = builtins.match "https://github\.com/([^/]*)/([^/]*)/?" url;
|
||||
in {
|
||||
owner = builtins.elemAt match 0;
|
||||
repo = builtins.elemAt match 1;
|
||||
};
|
||||
# If `use-grammars.only` is set, use only those grammars.
|
||||
# If `use-grammars.except` is set, use all other grammars.
|
||||
# Otherwise use all grammars.
|
||||
useGrammar = grammar:
|
||||
if languagesConfig ? use-grammars.only
|
||||
then builtins.elem grammar.name languagesConfig.use-grammars.only
|
||||
else if languagesConfig ? use-grammars.except
|
||||
then !(builtins.elem grammar.name languagesConfig.use-grammars.except)
|
||||
else true;
|
||||
grammarsToUse = builtins.filter useGrammar languagesConfig.grammar;
|
||||
gitGrammars = builtins.filter isGitGrammar grammarsToUse;
|
||||
buildGrammar = grammar: let
|
||||
gh = toGitHubFetcher grammar.source.git;
|
||||
sourceGit = builtins.fetchTree {
|
||||
builtins.fromJSON (builtins.readFile (builtins.toPath languages-json));
|
||||
isGitGrammar = (grammar:
|
||||
builtins.hasAttr "source" grammar && builtins.hasAttr "git" grammar.source
|
||||
&& builtins.hasAttr "rev" grammar.source);
|
||||
gitGrammars = builtins.filter isGitGrammar languagesConfig.grammar;
|
||||
buildGrammar = grammar:
|
||||
let
|
||||
source = builtins.fetchTree {
|
||||
type = "git";
|
||||
url = grammar.source.git;
|
||||
rev = grammar.source.rev;
|
||||
ref = grammar.source.ref or "HEAD";
|
||||
shallow = true;
|
||||
};
|
||||
sourceGitHub = builtins.fetchTree {
|
||||
type = "github";
|
||||
owner = gh.owner;
|
||||
repo = gh.repo;
|
||||
inherit (grammar.source) rev;
|
||||
};
|
||||
source =
|
||||
if isGitHubGrammar grammar
|
||||
then sourceGitHub
|
||||
else sourceGit;
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
in stdenv.mkDerivation rec {
|
||||
# see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
|
||||
|
||||
pname = "helix-tree-sitter-${grammar.name}";
|
||||
version = grammar.source.rev;
|
||||
|
||||
src = source;
|
||||
sourceRoot =
|
||||
if builtins.hasAttr "subpath" grammar.source
|
||||
then "source/${grammar.source.subpath}"
|
||||
else "source";
|
||||
src = if builtins.hasAttr "subpath" grammar.source then
|
||||
"${source}/${grammar.source.subpath}"
|
||||
else
|
||||
source;
|
||||
|
||||
dontUnpack = true;
|
||||
dontConfigure = true;
|
||||
|
||||
FLAGS = [
|
||||
"-Isrc"
|
||||
"-I${src}/src"
|
||||
"-g"
|
||||
"-O3"
|
||||
"-fPIC"
|
||||
@@ -78,15 +48,17 @@
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
if [[ -e src/scanner.cc ]]; then
|
||||
$CXX -c src/scanner.cc -o scanner.o $FLAGS
|
||||
elif [[ -e src/scanner.c ]]; then
|
||||
$CC -c src/scanner.c -o scanner.o $FLAGS
|
||||
if [[ -e "$src/src/scanner.cc" ]]; then
|
||||
$CXX -c "$src/src/scanner.cc" -o scanner.o $FLAGS
|
||||
elif [[ -e "$src/src/scanner.c" ]]; then
|
||||
$CC -c "$src/src/scanner.c" -o scanner.o $FLAGS
|
||||
fi
|
||||
|
||||
$CC -c src/parser.c -o parser.o $FLAGS
|
||||
$CC -c "$src/src/parser.c" -o parser.o $FLAGS
|
||||
$CXX -shared -o $NAME.so *.o
|
||||
|
||||
ls -al
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
@@ -104,24 +76,14 @@
|
||||
runHook postFixup
|
||||
'';
|
||||
};
|
||||
grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
|
||||
builtGrammars =
|
||||
builtins.map (grammar: {
|
||||
builtGrammars = builtins.map (grammar: {
|
||||
inherit (grammar) name;
|
||||
value = buildGrammar grammar;
|
||||
})
|
||||
grammarsToBuild;
|
||||
extensibleGrammars =
|
||||
lib.makeExtensible (self: builtins.listToAttrs builtGrammars);
|
||||
overlaidGrammars =
|
||||
lib.pipe extensibleGrammars
|
||||
(builtins.map (overlay: grammar: grammar.extend overlay) grammarOverlays);
|
||||
grammarLinks =
|
||||
lib.mapAttrsToList
|
||||
(name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
|
||||
(lib.filterAttrs (n: v: lib.isDerivation v) overlaidGrammars);
|
||||
in
|
||||
runCommand "consolidated-helix-grammars" {} ''
|
||||
artifact = buildGrammar grammar;
|
||||
}) gitGrammars;
|
||||
grammarLinks = builtins.map (grammar:
|
||||
"ln -s ${grammar.artifact}/${grammar.name}.so $out/${grammar.name}.so")
|
||||
builtGrammars;
|
||||
in runCommand "consolidated-helix-grammars" { } ''
|
||||
mkdir -p $out
|
||||
${builtins.concatStringsSep "\n" grammarLinks}
|
||||
''
|
||||
|
@@ -1,63 +1,45 @@
|
||||
[package]
|
||||
name = "helix-core"
|
||||
version = "0.6.0"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
description = "Helix editor core editing primitives"
|
||||
categories = ["editor"]
|
||||
repository = "https://github.com/helix-editor/helix"
|
||||
homepage = "https://helix-editor.com"
|
||||
include = ["src/**/*", "README.md"]
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
categories.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
[features]
|
||||
unicode-lines = ["ropey/unicode_lines"]
|
||||
integration = []
|
||||
|
||||
[dependencies]
|
||||
helix-stdx = { path = "../helix-stdx" }
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
helix-parsec = { path = "../helix-parsec" }
|
||||
helix-loader = { version = "0.6", path = "../helix-loader" }
|
||||
|
||||
ropey.workspace = true
|
||||
smallvec = "1.15"
|
||||
smartstring = "1.0.1"
|
||||
unicode-segmentation.workspace = true
|
||||
# unicode-width is changing width definitions
|
||||
# that both break our logic and disagree with common
|
||||
# width definitions in terminals, we need to replace it.
|
||||
# For now lets lock the version to avoid rendering glitches
|
||||
# when installing without `--locked`
|
||||
unicode-width = "=0.1.12"
|
||||
unicode-general-category = "1.1"
|
||||
slotmap.workspace = true
|
||||
tree-house.workspace = true
|
||||
once_cell = "1.21"
|
||||
ropey = "1.3"
|
||||
smallvec = "1.8"
|
||||
smartstring = "1.0.0"
|
||||
unicode-segmentation = "1.9"
|
||||
unicode-width = "0.1"
|
||||
unicode-general-category = "0.5"
|
||||
# slab = "0.4.2"
|
||||
slotmap = "1.0"
|
||||
tree-sitter = "0.20"
|
||||
once_cell = "1.10"
|
||||
arc-swap = "1"
|
||||
regex = "1"
|
||||
bitflags.workspace = true
|
||||
foldhash.workspace = true
|
||||
url = "2.5.4"
|
||||
|
||||
log = "0.4"
|
||||
anyhow = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml.workspace = true
|
||||
toml = "0.5"
|
||||
|
||||
similar = "2.1"
|
||||
|
||||
imara-diff = "0.2.0"
|
||||
encoding_rs = "0.8"
|
||||
|
||||
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
|
||||
|
||||
textwrap = "0.16.2"
|
||||
|
||||
nucleo.workspace = true
|
||||
parking_lot.workspace = true
|
||||
globset = "0.4.16"
|
||||
regex-cursor = "0.1.5"
|
||||
etcetera = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = { version = "1", default-features = false }
|
||||
indoc = "2.0.6"
|
||||
|
@@ -1,12 +1,16 @@
|
||||
//! When typing the opening character of one of the possible pairs defined below,
|
||||
//! this module provides the functionality to insert the paired closing character.
|
||||
|
||||
use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
|
||||
use crate::{
|
||||
graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use log::debug;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
// Heavily based on https://github.com/codemirror/closebrackets/
|
||||
|
||||
pub const DEFAULT_PAIRS: &[(char, char)] = &[
|
||||
('(', ')'),
|
||||
('{', '}'),
|
||||
@@ -17,7 +21,7 @@ pub const DEFAULT_PAIRS: &[(char, char)] = &[
|
||||
];
|
||||
|
||||
/// The type that represents the collection of auto pairs,
|
||||
/// keyed by both opener and closer.
|
||||
/// keyed by the opener.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AutoPairs(HashMap<char, Pair>);
|
||||
|
||||
@@ -75,9 +79,9 @@ impl From<(&char, &char)> for Pair {
|
||||
|
||||
impl AutoPairs {
|
||||
/// Make a new AutoPairs set with the given pairs and default conditions.
|
||||
pub fn new<'a, V, A>(pairs: V) -> Self
|
||||
pub fn new<'a, V: 'a, A>(pairs: V) -> Self
|
||||
where
|
||||
V: IntoIterator<Item = A> + 'a,
|
||||
V: IntoIterator<Item = A>,
|
||||
A: Into<Pair>,
|
||||
{
|
||||
let mut auto_pairs = HashMap::new();
|
||||
@@ -121,7 +125,7 @@ impl Default for AutoPairs {
|
||||
|
||||
#[must_use]
|
||||
pub fn hook(doc: &Rope, selection: &Selection, ch: char, pairs: &AutoPairs) -> Option<Transaction> {
|
||||
log::trace!("autopairs hook selection: {:#?}", selection);
|
||||
debug!("autopairs hook selection: {:#?}", selection);
|
||||
|
||||
if let Some(pair) = pairs.get(ch) {
|
||||
if pair.same() {
|
||||
@@ -145,8 +149,22 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
|
||||
doc.get_char(pos - 1)
|
||||
}
|
||||
|
||||
fn is_single_grapheme(doc: &Rope, range: &Range) -> bool {
|
||||
let mut graphemes = RopeGraphemes::new(doc.slice(range.from()..range.to()));
|
||||
let first = graphemes.next();
|
||||
let second = graphemes.next();
|
||||
debug!("first: {:#?}, second: {:#?}", first, second);
|
||||
first.is_some() && second.is_none()
|
||||
}
|
||||
|
||||
/// calculate what the resulting range should be for an auto pair insertion
|
||||
fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted: usize) -> Range {
|
||||
fn get_next_range(
|
||||
doc: &Rope,
|
||||
start_range: &Range,
|
||||
offset: usize,
|
||||
typed_char: char,
|
||||
len_inserted: usize,
|
||||
) -> Range {
|
||||
// When the character under the cursor changes due to complete pair
|
||||
// insertion, we must look backward a grapheme and then add the length
|
||||
// of the insertion to put the resulting cursor in the right place, e.g.
|
||||
@@ -166,13 +184,13 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
|
||||
// inserting at the very end of the document after the last newline
|
||||
if start_range.head == doc.len_chars() && start_range.anchor == doc.len_chars() {
|
||||
return Range::new(
|
||||
start_range.anchor + offset + 1,
|
||||
start_range.head + offset + 1,
|
||||
start_range.anchor + offset + typed_char.len_utf8(),
|
||||
start_range.head + offset + typed_char.len_utf8(),
|
||||
);
|
||||
}
|
||||
|
||||
let single_grapheme = is_single_grapheme(doc, start_range);
|
||||
let doc_slice = doc.slice(..);
|
||||
let single_grapheme = start_range.is_single_grapheme(doc_slice);
|
||||
|
||||
// just skip over graphemes
|
||||
if len_inserted == 0 {
|
||||
@@ -197,28 +215,29 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
|
||||
// trivial case: only inserted a single-char opener, just move the selection
|
||||
if len_inserted == 1 {
|
||||
let end_anchor = if single_grapheme || start_range.direction() == Direction::Backward {
|
||||
start_range.anchor + offset + 1
|
||||
start_range.anchor + offset + typed_char.len_utf8()
|
||||
} else {
|
||||
start_range.anchor + offset
|
||||
};
|
||||
|
||||
return Range::new(end_anchor, start_range.head + offset + 1);
|
||||
return Range::new(
|
||||
end_anchor,
|
||||
start_range.head + offset + typed_char.len_utf8(),
|
||||
);
|
||||
}
|
||||
|
||||
// If the head = 0, then we must be in insert mode with a backward
|
||||
// cursor, which implies the head will just move
|
||||
let end_head = if start_range.head == 0 || start_range.direction() == Direction::Backward {
|
||||
start_range.head + offset + 1
|
||||
start_range.head + offset + typed_char.len_utf8()
|
||||
} else {
|
||||
// We must have a forward cursor, which means we must move to the
|
||||
// other end of the grapheme to get to where the new characters
|
||||
// are inserted, then move the head to where it should be
|
||||
let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head);
|
||||
log::trace!(
|
||||
debug!(
|
||||
"prev_bound: {}, offset: {}, len_inserted: {}",
|
||||
prev_bound,
|
||||
offset,
|
||||
len_inserted
|
||||
prev_bound, offset, len_inserted
|
||||
);
|
||||
prev_bound + offset + len_inserted
|
||||
};
|
||||
@@ -234,7 +253,8 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
|
||||
|
||||
(_, Direction::Forward) => {
|
||||
if single_grapheme {
|
||||
graphemes::prev_grapheme_boundary(doc.slice(..), start_range.head) + 1
|
||||
graphemes::prev_grapheme_boundary(doc.slice(..), start_range.head)
|
||||
+ typed_char.len_utf8()
|
||||
|
||||
// if we are appending, the anchor stays where it is; only offset
|
||||
// for multiple range insertions
|
||||
@@ -248,9 +268,7 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
|
||||
// if we're backward, then the head is at the first char
|
||||
// of the typed char, so we need to add the length of
|
||||
// the closing char
|
||||
graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor)
|
||||
+ len_inserted
|
||||
+ offset
|
||||
graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor) + len_inserted
|
||||
} else {
|
||||
// when we are inserting in front of a selection, we need to move
|
||||
// the anchor over by however many characters were inserted overall
|
||||
@@ -271,12 +289,9 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
let next_char = doc.get_char(cursor);
|
||||
let len_inserted;
|
||||
|
||||
// Since auto pairs are currently limited to single chars, we're either
|
||||
// inserting exactly one or two chars. When arbitrary length pairs are
|
||||
// added, these will need to be changed.
|
||||
let change = match next_char {
|
||||
Some(_) if !pair.should_close(doc, start_range) => {
|
||||
len_inserted = 1;
|
||||
len_inserted = pair.open.len_utf8();
|
||||
let mut tendril = Tendril::new();
|
||||
tendril.push(pair.open);
|
||||
(cursor, cursor, Some(tendril))
|
||||
@@ -284,12 +299,12 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
_ => {
|
||||
// insert open & close
|
||||
let pair_str = Tendril::from_iter([pair.open, pair.close]);
|
||||
len_inserted = 2;
|
||||
len_inserted = pair.open.len_utf8() + pair.close.len_utf8();
|
||||
(cursor, cursor, Some(pair_str))
|
||||
}
|
||||
};
|
||||
|
||||
let next_range = get_next_range(doc, start_range, offs, len_inserted);
|
||||
let next_range = get_next_range(doc, start_range, offs, pair.open, len_inserted);
|
||||
end_ranges.push(next_range);
|
||||
offs += len_inserted;
|
||||
|
||||
@@ -297,12 +312,13 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
let mut end_ranges = SmallVec::with_capacity(selection.len());
|
||||
|
||||
let mut offs = 0;
|
||||
|
||||
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
|
||||
@@ -314,13 +330,13 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
// return transaction that moves past close
|
||||
(cursor, cursor, None) // no-op
|
||||
} else {
|
||||
len_inserted = 1;
|
||||
len_inserted += pair.close.len_utf8();
|
||||
let mut tendril = Tendril::new();
|
||||
tendril.push(pair.close);
|
||||
(cursor, cursor, Some(tendril))
|
||||
};
|
||||
|
||||
let next_range = get_next_range(doc, start_range, offs, len_inserted);
|
||||
let next_range = get_next_range(doc, start_range, offs, pair.close, len_inserted);
|
||||
end_ranges.push(next_range);
|
||||
offs += len_inserted;
|
||||
|
||||
@@ -328,7 +344,7 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
@@ -356,11 +372,11 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
pair_str.push(pair.close);
|
||||
}
|
||||
|
||||
len_inserted += pair_str.chars().count();
|
||||
len_inserted += pair_str.len();
|
||||
(cursor, cursor, Some(pair_str))
|
||||
};
|
||||
|
||||
let next_range = get_next_range(doc, start_range, offs, len_inserted);
|
||||
let next_range = get_next_range(doc, start_range, offs, pair.open, len_inserted);
|
||||
end_ranges.push(next_range);
|
||||
offs += len_inserted;
|
||||
|
||||
@@ -368,6 +384,554 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
||||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
log::debug!("auto pair transaction: {:#?}", t);
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use smallvec::smallvec;
|
||||
|
||||
const LINE_END: &str = crate::DEFAULT_LINE_ENDING.as_str();
|
||||
|
||||
fn differing_pairs() -> impl Iterator<Item = &'static (char, char)> {
|
||||
DEFAULT_PAIRS.iter().filter(|(open, close)| open != close)
|
||||
}
|
||||
|
||||
fn matching_pairs() -> impl Iterator<Item = &'static (char, char)> {
|
||||
DEFAULT_PAIRS.iter().filter(|(open, close)| open == close)
|
||||
}
|
||||
|
||||
fn test_hooks(
|
||||
in_doc: &Rope,
|
||||
in_sel: &Selection,
|
||||
ch: char,
|
||||
pairs: &[(char, char)],
|
||||
expected_doc: &Rope,
|
||||
expected_sel: &Selection,
|
||||
) {
|
||||
let pairs = AutoPairs::new(pairs.iter());
|
||||
let trans = hook(in_doc, in_sel, ch, &pairs).unwrap();
|
||||
let mut actual_doc = in_doc.clone();
|
||||
assert!(trans.apply(&mut actual_doc));
|
||||
assert_eq!(expected_doc, &actual_doc);
|
||||
assert_eq!(expected_sel, trans.selection().unwrap());
|
||||
}
|
||||
|
||||
fn test_hooks_with_pairs<I, F, R>(
|
||||
in_doc: &Rope,
|
||||
in_sel: &Selection,
|
||||
test_pairs: I,
|
||||
pairs: &[(char, char)],
|
||||
get_expected_doc: F,
|
||||
actual_sel: &Selection,
|
||||
) where
|
||||
I: IntoIterator<Item = &'static (char, char)>,
|
||||
F: Fn(char, char) -> R,
|
||||
R: Into<Rope>,
|
||||
Rope: From<R>,
|
||||
{
|
||||
test_pairs.into_iter().for_each(|(open, close)| {
|
||||
test_hooks(
|
||||
in_doc,
|
||||
in_sel,
|
||||
*open,
|
||||
pairs,
|
||||
&Rope::from(get_expected_doc(*open, *close)),
|
||||
actual_sel,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// [] indicates range
|
||||
|
||||
/// [] -> insert ( -> ([])
|
||||
#[test]
|
||||
fn test_insert_blank() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from(LINE_END),
|
||||
&Selection::single(1, 0),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| format!("{}{}{}", open, close, LINE_END),
|
||||
&Selection::single(2, 1),
|
||||
);
|
||||
|
||||
let empty_doc = Rope::from(format!("{line_end}{line_end}", line_end = LINE_END));
|
||||
|
||||
test_hooks_with_pairs(
|
||||
&empty_doc,
|
||||
&Selection::single(empty_doc.len_chars(), LINE_END.len()),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| {
|
||||
format!(
|
||||
"{line_end}{open}{close}{line_end}",
|
||||
open = open,
|
||||
close = close,
|
||||
line_end = LINE_END
|
||||
)
|
||||
},
|
||||
&Selection::single(LINE_END.len() + 2, LINE_END.len() + 1),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_before_multi_code_point_graphemes() {
|
||||
for (_, close) in differing_pairs() {
|
||||
test_hooks(
|
||||
&Rope::from(format!("hello 👨👩👧👦 goodbye{}", LINE_END)),
|
||||
&Selection::single(13, 6),
|
||||
*close,
|
||||
DEFAULT_PAIRS,
|
||||
&Rope::from(format!("hello {}👨👩👧👦 goodbye{}", close, LINE_END)),
|
||||
&Selection::single(14, 7),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_at_end_of_document() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from(LINE_END),
|
||||
&Selection::single(LINE_END.len(), LINE_END.len()),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| format!("{}{}{}", LINE_END, open, close),
|
||||
&Selection::single(LINE_END.len() + 1, LINE_END.len() + 1),
|
||||
);
|
||||
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from(format!("foo{}", LINE_END)),
|
||||
&Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| format!("foo{}{}{}", LINE_END, open, close),
|
||||
&Selection::single(LINE_END.len() + 4, LINE_END.len() + 4),
|
||||
);
|
||||
}
|
||||
|
||||
/// [] -> append ( -> ([])
|
||||
#[test]
|
||||
fn test_append_blank() {
|
||||
test_hooks_with_pairs(
|
||||
// this is what happens when you have a totally blank document and then append
|
||||
&Rope::from(format!("{line_end}{line_end}", line_end = LINE_END)),
|
||||
// before inserting the pair, the cursor covers all of both empty lines
|
||||
&Selection::single(0, LINE_END.len() * 2),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| {
|
||||
format!(
|
||||
"{line_end}{open}{close}{line_end}",
|
||||
line_end = LINE_END,
|
||||
open = open,
|
||||
close = close
|
||||
)
|
||||
},
|
||||
// after inserting pair, the cursor covers the first new line and the open char
|
||||
&Selection::single(0, LINE_END.len() + 2),
|
||||
);
|
||||
}
|
||||
|
||||
/// [] ([])
|
||||
/// [] -> insert -> ([])
|
||||
/// [] ([])
|
||||
#[test]
|
||||
fn test_insert_blank_multi_cursor() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("\n\n\n"),
|
||||
&Selection::new(
|
||||
smallvec!(Range::new(1, 0), Range::new(2, 1), Range::new(3, 2),),
|
||||
0,
|
||||
),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| {
|
||||
format!(
|
||||
"{open}{close}\n{open}{close}\n{open}{close}\n",
|
||||
open = open,
|
||||
close = close
|
||||
)
|
||||
},
|
||||
&Selection::new(
|
||||
smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
|
||||
0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// fo[o] -> append ( -> fo[o(])
|
||||
#[test]
|
||||
fn test_append() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("foo\n"),
|
||||
&Selection::single(2, 4),
|
||||
differing_pairs(),
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| format!("foo{}{}\n", open, close),
|
||||
&Selection::single(2, 5),
|
||||
);
|
||||
}
|
||||
|
||||
/// foo[] -> append to end of line ( -> foo([])
|
||||
#[test]
|
||||
fn test_append_single_cursor() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from(format!("foo{}", LINE_END)),
|
||||
&Selection::single(3, 3 + LINE_END.len()),
|
||||
differing_pairs(),
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| format!("foo{}{}{}", open, close, LINE_END),
|
||||
&Selection::single(4, 5),
|
||||
);
|
||||
}
|
||||
|
||||
/// fo[o] fo[o(])
|
||||
/// fo[o] -> append ( -> fo[o(])
|
||||
/// fo[o] fo[o(])
|
||||
#[test]
|
||||
fn test_append_multi() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("foo\nfoo\nfoo\n"),
|
||||
&Selection::new(
|
||||
smallvec!(Range::new(2, 4), Range::new(6, 8), Range::new(10, 12)),
|
||||
0,
|
||||
),
|
||||
differing_pairs(),
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| {
|
||||
format!(
|
||||
"foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
|
||||
open = open,
|
||||
close = close
|
||||
)
|
||||
},
|
||||
&Selection::new(
|
||||
smallvec!(Range::new(2, 5), Range::new(8, 11), Range::new(14, 17)),
|
||||
0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// ([)] -> insert ) -> ()[]
|
||||
#[test]
|
||||
fn test_insert_close_inside_pair() {
|
||||
for (open, close) in DEFAULT_PAIRS {
|
||||
let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&Selection::single(2, 1),
|
||||
*close,
|
||||
DEFAULT_PAIRS,
|
||||
&doc,
|
||||
&Selection::single(2 + LINE_END.len(), 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [(]) -> append ) -> [()]
|
||||
#[test]
|
||||
fn test_append_close_inside_pair() {
|
||||
for (open, close) in DEFAULT_PAIRS {
|
||||
let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&Selection::single(0, 2),
|
||||
*close,
|
||||
DEFAULT_PAIRS,
|
||||
&doc,
|
||||
&Selection::single(0, 2 + LINE_END.len()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ([]) ()[]
|
||||
/// ([]) -> insert ) -> ()[]
|
||||
/// ([]) ()[]
|
||||
#[test]
|
||||
fn test_insert_close_inside_pair_multi_cursor() {
|
||||
let sel = Selection::new(
|
||||
smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
|
||||
0,
|
||||
);
|
||||
|
||||
let expected_sel = Selection::new(
|
||||
smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
|
||||
0,
|
||||
);
|
||||
|
||||
for (open, close) in DEFAULT_PAIRS {
|
||||
let doc = Rope::from(format!(
|
||||
"{open}{close}\n{open}{close}\n{open}{close}\n",
|
||||
open = open,
|
||||
close = close
|
||||
));
|
||||
|
||||
test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
|
||||
}
|
||||
}
|
||||
|
||||
/// [(]) [()]
|
||||
/// [(]) -> append ) -> [()]
|
||||
/// [(]) [()]
|
||||
#[test]
|
||||
fn test_append_close_inside_pair_multi_cursor() {
|
||||
let sel = Selection::new(
|
||||
smallvec!(Range::new(0, 2), Range::new(3, 5), Range::new(6, 8),),
|
||||
0,
|
||||
);
|
||||
|
||||
let expected_sel = Selection::new(
|
||||
smallvec!(Range::new(0, 3), Range::new(3, 6), Range::new(6, 9),),
|
||||
0,
|
||||
);
|
||||
|
||||
for (open, close) in DEFAULT_PAIRS {
|
||||
let doc = Rope::from(format!(
|
||||
"{open}{close}\n{open}{close}\n{open}{close}\n",
|
||||
open = open,
|
||||
close = close
|
||||
));
|
||||
|
||||
test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
|
||||
}
|
||||
}
|
||||
|
||||
/// ([]) -> insert ( -> (([]))
|
||||
#[test]
|
||||
fn test_insert_open_inside_pair() {
|
||||
let sel = Selection::single(2, 1);
|
||||
let expected_sel = Selection::single(3, 2);
|
||||
|
||||
for (open, close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("{}{}", open, close));
|
||||
let expected_doc = Rope::from(format!(
|
||||
"{open}{open}{close}{close}",
|
||||
open = open,
|
||||
close = close
|
||||
));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&sel,
|
||||
*open,
|
||||
DEFAULT_PAIRS,
|
||||
&expected_doc,
|
||||
&expected_sel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [word(]) -> append ( -> [word((]))
|
||||
#[test]
|
||||
fn test_append_open_inside_pair() {
|
||||
let sel = Selection::single(0, 6);
|
||||
let expected_sel = Selection::single(0, 7);
|
||||
|
||||
for (open, close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("word{}{}", open, close));
|
||||
let expected_doc = Rope::from(format!(
|
||||
"word{open}{open}{close}{close}",
|
||||
open = open,
|
||||
close = close
|
||||
));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&sel,
|
||||
*open,
|
||||
DEFAULT_PAIRS,
|
||||
&expected_doc,
|
||||
&expected_sel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ([]) -> insert " -> ("[]")
|
||||
#[test]
|
||||
fn test_insert_nested_open_inside_pair() {
|
||||
let sel = Selection::single(2, 1);
|
||||
let expected_sel = Selection::single(3, 2);
|
||||
|
||||
for (outer_open, outer_close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
|
||||
|
||||
for (inner_open, inner_close) in matching_pairs() {
|
||||
let expected_doc = Rope::from(format!(
|
||||
"{}{}{}{}",
|
||||
outer_open, inner_open, inner_close, outer_close
|
||||
));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&sel,
|
||||
*inner_open,
|
||||
DEFAULT_PAIRS,
|
||||
&expected_doc,
|
||||
&expected_sel,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [(]) -> append " -> [("]")
|
||||
#[test]
|
||||
fn test_append_nested_open_inside_pair() {
|
||||
let sel = Selection::single(0, 2);
|
||||
let expected_sel = Selection::single(0, 3);
|
||||
|
||||
for (outer_open, outer_close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
|
||||
|
||||
for (inner_open, inner_close) in matching_pairs() {
|
||||
let expected_doc = Rope::from(format!(
|
||||
"{}{}{}{}",
|
||||
outer_open, inner_open, inner_close, outer_close
|
||||
));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&sel,
|
||||
*inner_open,
|
||||
DEFAULT_PAIRS,
|
||||
&expected_doc,
|
||||
&expected_sel,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// []word -> insert ( -> ([]word
|
||||
#[test]
|
||||
fn test_insert_open_before_non_pair() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("word"),
|
||||
&Selection::single(1, 0),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, _| format!("{}word", open),
|
||||
&Selection::single(2, 1),
|
||||
)
|
||||
}
|
||||
|
||||
/// [wor]d -> insert ( -> ([wor]d
|
||||
#[test]
|
||||
fn test_insert_open_with_selection() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("word"),
|
||||
&Selection::single(3, 0),
|
||||
DEFAULT_PAIRS,
|
||||
DEFAULT_PAIRS,
|
||||
|open, _| format!("{}word", open),
|
||||
&Selection::single(4, 1),
|
||||
)
|
||||
}
|
||||
|
||||
/// [wor]d -> append ) -> [wor)]d
|
||||
#[test]
|
||||
fn test_append_close_inside_non_pair_with_selection() {
|
||||
let sel = Selection::single(0, 4);
|
||||
let expected_sel = Selection::single(0, 5);
|
||||
|
||||
for (_, close) in DEFAULT_PAIRS {
|
||||
let doc = Rope::from("word");
|
||||
let expected_doc = Rope::from(format!("wor{}d", close));
|
||||
test_hooks(
|
||||
&doc,
|
||||
&sel,
|
||||
*close,
|
||||
DEFAULT_PAIRS,
|
||||
&expected_doc,
|
||||
&expected_sel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// foo[ wor]d -> insert ( -> foo([) wor]d
|
||||
#[test]
|
||||
fn test_insert_open_trailing_word_with_selection() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("foo word"),
|
||||
&Selection::single(7, 3),
|
||||
differing_pairs(),
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| format!("foo{}{} word", open, close),
|
||||
&Selection::single(9, 4),
|
||||
)
|
||||
}
|
||||
|
||||
/// foo([) wor]d -> insert ) -> foo()[ wor]d
|
||||
#[test]
|
||||
fn test_insert_close_inside_pair_trailing_word_with_selection() {
|
||||
for (open, close) in differing_pairs() {
|
||||
test_hooks(
|
||||
&Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
|
||||
&Selection::single(9, 4),
|
||||
*close,
|
||||
DEFAULT_PAIRS,
|
||||
&Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
|
||||
&Selection::single(9, 5),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// we want pairs that are *not* the same char to be inserted after
|
||||
/// a non-pair char, for cases like functions, but for pairs that are
|
||||
/// the same char, we want to *not* insert a pair to handle cases like "I'm"
|
||||
///
|
||||
/// word[] -> insert ( -> word([])
|
||||
/// word[] -> insert ' -> word'[]
|
||||
#[test]
|
||||
fn test_insert_open_after_non_pair() {
|
||||
let doc = Rope::from(format!("word{}", LINE_END));
|
||||
let sel = Selection::single(5, 4);
|
||||
let expected_sel = Selection::single(6, 5);
|
||||
|
||||
test_hooks_with_pairs(
|
||||
&doc,
|
||||
&sel,
|
||||
differing_pairs(),
|
||||
DEFAULT_PAIRS,
|
||||
|open, close| format!("word{}{}{}", open, close, LINE_END),
|
||||
&expected_sel,
|
||||
);
|
||||
|
||||
test_hooks_with_pairs(
|
||||
&doc,
|
||||
&sel,
|
||||
matching_pairs(),
|
||||
DEFAULT_PAIRS,
|
||||
|open, _| format!("word{}{}", open, LINE_END),
|
||||
&expected_sel,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configured_pairs() {
|
||||
let test_pairs = &[('`', ':'), ('+', '-')];
|
||||
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from(LINE_END),
|
||||
&Selection::single(1, 0),
|
||||
test_pairs,
|
||||
test_pairs,
|
||||
|open, close| format!("{}{}{}", open, close, LINE_END),
|
||||
&Selection::single(2, 1),
|
||||
);
|
||||
|
||||
let doc = Rope::from(format!("foo`: word{}", LINE_END));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&Selection::single(9, 4),
|
||||
':',
|
||||
test_pairs,
|
||||
&doc,
|
||||
&Selection::single(9, 5),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -1,69 +0,0 @@
|
||||
use crate::Tendril;
|
||||
|
||||
// todo: should this be grapheme aware?
|
||||
|
||||
pub fn to_pascal_case(text: impl Iterator<Item = char>) -> Tendril {
|
||||
let mut res = Tendril::new();
|
||||
to_pascal_case_with(text, &mut res);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn to_pascal_case_with(text: impl Iterator<Item = char>, buf: &mut Tendril) {
|
||||
let mut at_word_start = true;
|
||||
for c in text {
|
||||
// we don't count _ as a word char here so case conversions work well
|
||||
if !c.is_alphanumeric() {
|
||||
at_word_start = true;
|
||||
continue;
|
||||
}
|
||||
if at_word_start {
|
||||
at_word_start = false;
|
||||
buf.extend(c.to_uppercase());
|
||||
} else {
|
||||
buf.push(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_upper_case_with(text: impl Iterator<Item = char>, buf: &mut Tendril) {
|
||||
for c in text {
|
||||
for c in c.to_uppercase() {
|
||||
buf.push(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_lower_case_with(text: impl Iterator<Item = char>, buf: &mut Tendril) {
|
||||
for c in text {
|
||||
for c in c.to_lowercase() {
|
||||
buf.push(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_camel_case(text: impl Iterator<Item = char>) -> Tendril {
|
||||
let mut res = Tendril::new();
|
||||
to_camel_case_with(text, &mut res);
|
||||
res
|
||||
}
|
||||
pub fn to_camel_case_with(mut text: impl Iterator<Item = char>, buf: &mut Tendril) {
|
||||
for c in &mut text {
|
||||
if c.is_alphanumeric() {
|
||||
buf.extend(c.to_lowercase())
|
||||
}
|
||||
}
|
||||
let mut at_word_start = false;
|
||||
for c in text {
|
||||
// we don't count _ as a word char here so case conversions work well
|
||||
if !c.is_alphanumeric() {
|
||||
at_word_start = true;
|
||||
continue;
|
||||
}
|
||||
if at_word_start {
|
||||
at_word_start = false;
|
||||
buf.extend(c.to_uppercase());
|
||||
} else {
|
||||
buf.push(c)
|
||||
}
|
||||
}
|
||||
}
|
@@ -91,10 +91,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_categorize() {
|
||||
#[cfg(not(feature = "unicode-lines"))]
|
||||
const EOL_TEST_CASE: &str = "\n";
|
||||
#[cfg(feature = "unicode-lines")]
|
||||
const EOL_TEST_CASE: &str = "\n\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}";
|
||||
const EOL_TEST_CASE: &str = "\n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}";
|
||||
const WORD_TEST_CASE: &str = "_hello_world_あいうえおー12345678901234567890";
|
||||
const PUNCTUATION_TEST_CASE: &str =
|
||||
"!\"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~!”#$%&’()*+、。:;<=>?@「」^`{|}~";
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,33 +1,11 @@
|
||||
//! This module contains the functionality toggle comments on lines over the selection
|
||||
//! using the comment character defined in the user's `languages.toml`
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
syntax::config::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril,
|
||||
Transaction,
|
||||
find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction,
|
||||
};
|
||||
use helix_stdx::rope::RopeSliceExt;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub const DEFAULT_COMMENT_TOKEN: &str = "#";
|
||||
|
||||
/// Returns the longest matching comment token of the given line (if it exists).
|
||||
pub fn get_comment_token<'a, S: AsRef<str>>(
|
||||
text: RopeSlice,
|
||||
tokens: &'a [S],
|
||||
line_num: usize,
|
||||
) -> Option<&'a str> {
|
||||
let line = text.line(line_num);
|
||||
let start = line.first_non_whitespace_char()?;
|
||||
|
||||
tokens
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.filter(|token| line.slice(start..).starts_with(token))
|
||||
.max_by_key(|token| token.len())
|
||||
}
|
||||
|
||||
/// Given text, a comment token, and a set of line indices, returns the following:
|
||||
/// - Whether the given lines should be considered commented
|
||||
/// - If any of the lines are uncommented, all lines are considered as such.
|
||||
@@ -44,29 +22,30 @@ fn find_line_comment(
|
||||
) -> (bool, Vec<usize>, usize, usize) {
|
||||
let mut commented = true;
|
||||
let mut to_change = Vec::new();
|
||||
let mut min = usize::MAX; // minimum col for first_non_whitespace_char
|
||||
let mut min = usize::MAX; // minimum col for find_first_non_whitespace_char
|
||||
let mut margin = 1;
|
||||
let token_len = token.chars().count();
|
||||
|
||||
for line in lines {
|
||||
let line_slice = text.line(line);
|
||||
if let Some(pos) = line_slice.first_non_whitespace_char() {
|
||||
if let Some(pos) = find_first_non_whitespace_char(line_slice) {
|
||||
let len = line_slice.len_chars();
|
||||
|
||||
min = std::cmp::min(min, pos);
|
||||
if pos < min {
|
||||
min = pos;
|
||||
}
|
||||
|
||||
// line can be shorter than pos + token len
|
||||
let fragment = Cow::from(line_slice.slice(pos..std::cmp::min(pos + token.len(), len)));
|
||||
|
||||
if fragment != token {
|
||||
// as soon as one of the non-blank lines doesn't have a comment, the whole block is
|
||||
// considered uncommented.
|
||||
if fragment != token {
|
||||
commented = false;
|
||||
}
|
||||
|
||||
// determine margin of 0 or 1 for uncommenting; if any comment token is not followed by a space,
|
||||
// a margin of 0 is used for all lines.
|
||||
if !matches!(line_slice.get_char(pos + token_len), Some(c) if c == ' ') {
|
||||
if matches!(line_slice.get_char(pos + token_len), Some(c) if c != ' ') {
|
||||
margin = 0;
|
||||
}
|
||||
|
||||
@@ -74,7 +53,6 @@ fn find_line_comment(
|
||||
to_change.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
(commented, to_change, min, margin)
|
||||
}
|
||||
|
||||
@@ -82,7 +60,7 @@ fn find_line_comment(
|
||||
pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&str>) -> Transaction {
|
||||
let text = doc.slice(..);
|
||||
|
||||
let token = token.unwrap_or(DEFAULT_COMMENT_TOKEN);
|
||||
let token = token.unwrap_or("//");
|
||||
let comment = Tendril::from(format!("{} ", token));
|
||||
|
||||
let mut lines: Vec<usize> = Vec::with_capacity(selection.len());
|
||||
@@ -90,11 +68,11 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st
|
||||
let mut min_next_line = 0;
|
||||
for selection in selection {
|
||||
let (start, end) = selection.line_range(text);
|
||||
let start = start.clamp(min_next_line, text.len_lines());
|
||||
let start = start.max(min_next_line).min(text.len_lines());
|
||||
let end = (end + 1).min(text.len_lines());
|
||||
|
||||
lines.extend(start..end);
|
||||
min_next_line = end;
|
||||
min_next_line = end + 1;
|
||||
}
|
||||
|
||||
let (commented, to_change, min, margin) = find_line_comment(token, text, lines);
|
||||
@@ -116,372 +94,50 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st
|
||||
Transaction::change(doc, changes.into_iter())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum CommentChange {
|
||||
Commented {
|
||||
range: Range,
|
||||
start_pos: usize,
|
||||
end_pos: usize,
|
||||
start_margin: bool,
|
||||
end_margin: bool,
|
||||
start_token: String,
|
||||
end_token: String,
|
||||
},
|
||||
Uncommented {
|
||||
range: Range,
|
||||
start_pos: usize,
|
||||
end_pos: usize,
|
||||
start_token: String,
|
||||
end_token: String,
|
||||
},
|
||||
Whitespace {
|
||||
range: Range,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn find_block_comments(
|
||||
tokens: &[BlockCommentToken],
|
||||
text: RopeSlice,
|
||||
selection: &Selection,
|
||||
) -> (bool, Vec<CommentChange>) {
|
||||
let mut commented = true;
|
||||
let mut only_whitespace = true;
|
||||
let mut comment_changes = Vec::with_capacity(selection.len());
|
||||
let default_tokens = tokens.first().cloned().unwrap_or_default();
|
||||
let mut start_token = default_tokens.start.clone();
|
||||
let mut end_token = default_tokens.end.clone();
|
||||
|
||||
let mut tokens = tokens.to_vec();
|
||||
// sort the tokens by length, so longer tokens will match first
|
||||
tokens.sort_by(|a, b| {
|
||||
if a.start.len() == b.start.len() {
|
||||
b.end.len().cmp(&a.end.len())
|
||||
} else {
|
||||
b.start.len().cmp(&a.start.len())
|
||||
}
|
||||
});
|
||||
for range in selection {
|
||||
let selection_slice = range.slice(text);
|
||||
if let (Some(start_pos), Some(end_pos)) = (
|
||||
selection_slice.first_non_whitespace_char(),
|
||||
selection_slice.last_non_whitespace_char(),
|
||||
) {
|
||||
let mut line_commented = false;
|
||||
let mut after_start = 0;
|
||||
let mut before_end = 0;
|
||||
let len = (end_pos + 1) - start_pos;
|
||||
|
||||
for BlockCommentToken { start, end } in &tokens {
|
||||
let start_len = start.chars().count();
|
||||
let end_len = end.chars().count();
|
||||
after_start = start_pos + start_len;
|
||||
before_end = end_pos.saturating_sub(end_len);
|
||||
|
||||
if len >= start_len + end_len {
|
||||
let start_fragment = selection_slice.slice(start_pos..after_start);
|
||||
let end_fragment = selection_slice.slice(before_end + 1..end_pos + 1);
|
||||
|
||||
// block commented with these tokens
|
||||
if start_fragment == start.as_str() && end_fragment == end.as_str() {
|
||||
start_token = start.to_string();
|
||||
end_token = end.to_string();
|
||||
line_commented = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !line_commented {
|
||||
comment_changes.push(CommentChange::Uncommented {
|
||||
range: *range,
|
||||
start_pos,
|
||||
end_pos,
|
||||
start_token: default_tokens.start.clone(),
|
||||
end_token: default_tokens.end.clone(),
|
||||
});
|
||||
commented = false;
|
||||
} else {
|
||||
comment_changes.push(CommentChange::Commented {
|
||||
range: *range,
|
||||
start_pos,
|
||||
end_pos,
|
||||
start_margin: selection_slice.get_char(after_start) == Some(' '),
|
||||
end_margin: after_start != before_end
|
||||
&& (selection_slice.get_char(before_end) == Some(' ')),
|
||||
start_token: start_token.to_string(),
|
||||
end_token: end_token.to_string(),
|
||||
});
|
||||
}
|
||||
only_whitespace = false;
|
||||
} else {
|
||||
comment_changes.push(CommentChange::Whitespace { range: *range });
|
||||
}
|
||||
}
|
||||
if only_whitespace {
|
||||
commented = false;
|
||||
}
|
||||
(commented, comment_changes)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn create_block_comment_transaction(
|
||||
doc: &Rope,
|
||||
selection: &Selection,
|
||||
commented: bool,
|
||||
comment_changes: Vec<CommentChange>,
|
||||
) -> (Transaction, SmallVec<[Range; 1]>) {
|
||||
let mut changes: Vec<Change> = Vec::with_capacity(selection.len() * 2);
|
||||
let mut ranges: SmallVec<[Range; 1]> = SmallVec::with_capacity(selection.len());
|
||||
let mut offs = 0;
|
||||
for change in comment_changes {
|
||||
if commented {
|
||||
if let CommentChange::Commented {
|
||||
range,
|
||||
start_pos,
|
||||
end_pos,
|
||||
start_token,
|
||||
end_token,
|
||||
start_margin,
|
||||
end_margin,
|
||||
} = change
|
||||
{
|
||||
let from = range.from();
|
||||
changes.push((
|
||||
from + start_pos,
|
||||
from + start_pos + start_token.len() + start_margin as usize,
|
||||
None,
|
||||
));
|
||||
changes.push((
|
||||
from + end_pos - end_token.len() - end_margin as usize + 1,
|
||||
from + end_pos + 1,
|
||||
None,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// uncommented so manually map ranges through changes
|
||||
match change {
|
||||
CommentChange::Uncommented {
|
||||
range,
|
||||
start_pos,
|
||||
end_pos,
|
||||
start_token,
|
||||
end_token,
|
||||
} => {
|
||||
let from = range.from();
|
||||
changes.push((
|
||||
from + start_pos,
|
||||
from + start_pos,
|
||||
Some(Tendril::from(format!("{} ", start_token))),
|
||||
));
|
||||
changes.push((
|
||||
from + end_pos + 1,
|
||||
from + end_pos + 1,
|
||||
Some(Tendril::from(format!(" {}", end_token))),
|
||||
));
|
||||
|
||||
let offset = start_token.chars().count() + end_token.chars().count() + 2;
|
||||
ranges.push(
|
||||
Range::new(from + offs, from + offs + end_pos + 1 + offset)
|
||||
.with_direction(range.direction()),
|
||||
);
|
||||
offs += offset;
|
||||
}
|
||||
CommentChange::Commented { range, .. } | CommentChange::Whitespace { range } => {
|
||||
ranges.push(Range::new(range.from() + offs, range.to() + offs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(Transaction::change(doc, changes.into_iter()), ranges)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn toggle_block_comments(
|
||||
doc: &Rope,
|
||||
selection: &Selection,
|
||||
tokens: &[BlockCommentToken],
|
||||
) -> Transaction {
|
||||
let text = doc.slice(..);
|
||||
let (commented, comment_changes) = find_block_comments(tokens, text, selection);
|
||||
let (mut transaction, ranges) =
|
||||
create_block_comment_transaction(doc, selection, commented, comment_changes);
|
||||
if !commented {
|
||||
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
|
||||
}
|
||||
transaction
|
||||
}
|
||||
|
||||
pub fn split_lines_of_selection(text: RopeSlice, selection: &Selection) -> Selection {
|
||||
let mut ranges = SmallVec::new();
|
||||
for range in selection.ranges() {
|
||||
let (line_start, line_end) = range.line_range(text.slice(..));
|
||||
let mut pos = text.line_to_char(line_start);
|
||||
for line in text.slice(pos..text.line_to_char(line_end + 1)).lines() {
|
||||
let start = pos;
|
||||
pos += line.len_chars();
|
||||
ranges.push(Range::new(start, pos));
|
||||
}
|
||||
}
|
||||
Selection::new(ranges, 0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
mod find_line_comment {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn not_commented() {
|
||||
fn test_find_line_comment() {
|
||||
use crate::State;
|
||||
|
||||
// four lines, two space indented, except for line 1 which is blank.
|
||||
let doc = Rope::from(" 1\n\n 2\n 3");
|
||||
|
||||
let text = doc.slice(..);
|
||||
let mut state = State::new(doc);
|
||||
// select whole document
|
||||
state.selection = Selection::single(0, state.doc.len_chars() - 1);
|
||||
|
||||
let text = state.doc.slice(..);
|
||||
|
||||
let res = find_line_comment("//", text, 0..3);
|
||||
// (commented = false, to_change = [line 0, line 2], min = col 2, margin = 0)
|
||||
assert_eq!(res, (false, vec![0, 2], 2, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_commented() {
|
||||
// three lines where the second line is empty.
|
||||
let doc = Rope::from("// hello\n\n// there");
|
||||
|
||||
let res = find_line_comment("//", doc.slice(..), 0..3);
|
||||
|
||||
// (commented = true, to_change = [line 0, line 2], min = col 0, margin = 1)
|
||||
assert_eq!(res, (true, vec![0, 2], 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: account for uncommenting with uneven comment indentation
|
||||
mod toggle_line_comment {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn comment() {
|
||||
// four lines, two space indented, except for line 1 which is blank.
|
||||
let mut doc = Rope::from(" 1\n\n 2\n 3");
|
||||
// select whole document
|
||||
let selection = Selection::single(0, doc.len_chars() - 1);
|
||||
|
||||
let transaction = toggle_line_comments(&doc, &selection, None);
|
||||
transaction.apply(&mut doc);
|
||||
|
||||
assert_eq!(doc, " # 1\n\n # 2\n # 3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncomment() {
|
||||
let mut doc = Rope::from(" # 1\n\n # 2\n # 3");
|
||||
let mut selection = Selection::single(0, doc.len_chars() - 1);
|
||||
|
||||
let transaction = toggle_line_comments(&doc, &selection, None);
|
||||
transaction.apply(&mut doc);
|
||||
selection = selection.map(transaction.changes());
|
||||
|
||||
assert_eq!(doc, " 1\n\n 2\n 3");
|
||||
assert!(selection.len() == 1); // to ignore the selection unused warning
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncomment_0_margin_comments() {
|
||||
let mut doc = Rope::from(" #1\n\n #2\n #3");
|
||||
let mut selection = Selection::single(0, doc.len_chars() - 1);
|
||||
|
||||
let transaction = toggle_line_comments(&doc, &selection, None);
|
||||
transaction.apply(&mut doc);
|
||||
selection = selection.map(transaction.changes());
|
||||
|
||||
assert_eq!(doc, " 1\n\n 2\n 3");
|
||||
assert!(selection.len() == 1); // to ignore the selection unused warning
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncomment_0_margin_comments_with_no_space() {
|
||||
let mut doc = Rope::from("#");
|
||||
let mut selection = Selection::single(0, doc.len_chars() - 1);
|
||||
|
||||
let transaction = toggle_line_comments(&doc, &selection, None);
|
||||
transaction.apply(&mut doc);
|
||||
selection = selection.map(transaction.changes());
|
||||
assert_eq!(doc, "");
|
||||
assert!(selection.len() == 1); // to ignore the selection unused warning
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_block_comments() {
|
||||
// three lines 5 characters.
|
||||
let mut doc = Rope::from("1\n2\n3");
|
||||
// select whole document
|
||||
let selection = Selection::single(0, doc.len_chars());
|
||||
|
||||
let text = doc.slice(..);
|
||||
|
||||
let res = find_block_comments(&[BlockCommentToken::default()], text, &selection);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
(
|
||||
false,
|
||||
vec![CommentChange::Uncommented {
|
||||
range: Range::new(0, 5),
|
||||
start_pos: 0,
|
||||
end_pos: 4,
|
||||
start_token: "/*".to_string(),
|
||||
end_token: "*/".to_string(),
|
||||
}]
|
||||
)
|
||||
);
|
||||
// (commented = true, to_change = [line 0, line 2], min = col 2, margin = 1)
|
||||
assert_eq!(res, (false, vec![0, 2], 2, 1));
|
||||
|
||||
// comment
|
||||
let transaction = toggle_block_comments(&doc, &selection, &[BlockCommentToken::default()]);
|
||||
transaction.apply(&mut doc);
|
||||
let transaction = toggle_line_comments(&state.doc, &state.selection, None);
|
||||
transaction.apply(&mut state.doc);
|
||||
state.selection = state.selection.map(transaction.changes());
|
||||
|
||||
assert_eq!(doc, "/* 1\n2\n3 */");
|
||||
assert_eq!(state.doc, " // 1\n\n // 2\n // 3");
|
||||
|
||||
// uncomment
|
||||
let selection = Selection::single(0, doc.len_chars());
|
||||
let transaction = toggle_block_comments(&doc, &selection, &[BlockCommentToken::default()]);
|
||||
transaction.apply(&mut doc);
|
||||
assert_eq!(doc, "1\n2\n3");
|
||||
let transaction = toggle_line_comments(&state.doc, &state.selection, None);
|
||||
transaction.apply(&mut state.doc);
|
||||
state.selection = state.selection.map(transaction.changes());
|
||||
assert_eq!(state.doc, " 1\n\n 2\n 3");
|
||||
|
||||
// don't panic when there is just a space in comment
|
||||
doc = Rope::from("/* */");
|
||||
let selection = Selection::single(0, doc.len_chars());
|
||||
let transaction = toggle_block_comments(&doc, &selection, &[BlockCommentToken::default()]);
|
||||
transaction.apply(&mut doc);
|
||||
assert_eq!(doc, "");
|
||||
}
|
||||
// 0 margin comments
|
||||
state.doc = Rope::from(" //1\n\n //2\n //3");
|
||||
// reset the selection.
|
||||
state.selection = Selection::single(0, state.doc.len_chars() - 1);
|
||||
|
||||
/// Test, if `get_comment_tokens` works, even if the content of the file includes chars, whose
|
||||
/// byte size unequal the amount of chars
|
||||
#[test]
|
||||
fn test_get_comment_with_char_boundaries() {
|
||||
let rope = Rope::from("··");
|
||||
let tokens = ["//", "///"];
|
||||
let transaction = toggle_line_comments(&state.doc, &state.selection, None);
|
||||
transaction.apply(&mut state.doc);
|
||||
state.selection = state.selection.map(transaction.changes());
|
||||
assert_eq!(state.doc, " 1\n\n 2\n 3");
|
||||
|
||||
assert_eq!(
|
||||
super::get_comment_token(rope.slice(..), tokens.as_slice(), 0),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
/// Test for `get_comment_token`.
|
||||
///
|
||||
/// Assuming the comment tokens are stored as `["///", "//"]`, `get_comment_token` should still
|
||||
/// return `///` instead of `//` if the user is in a doc-comment section.
|
||||
#[test]
|
||||
fn test_use_longest_comment() {
|
||||
let text = Rope::from(" /// amogus");
|
||||
let tokens = ["///", "//"];
|
||||
|
||||
assert_eq!(
|
||||
super::get_comment_token(text.slice(..), tokens.as_slice(), 0),
|
||||
Some("///")
|
||||
);
|
||||
// TODO: account for uncommenting with uneven comment indentation
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{diagnostic::LanguageServerId, Transaction};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct CompletionItem {
|
||||
pub transaction: Transaction,
|
||||
pub label: Cow<'static, str>,
|
||||
pub kind: Cow<'static, str>,
|
||||
/// Containing Markdown
|
||||
pub documentation: Option<String>,
|
||||
pub provider: CompletionProvider,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum CompletionProvider {
|
||||
Lsp(LanguageServerId),
|
||||
Path,
|
||||
Word,
|
||||
}
|
||||
|
||||
impl From<LanguageServerId> for CompletionProvider {
|
||||
fn from(id: LanguageServerId) -> Self {
|
||||
CompletionProvider::Lsp(id)
|
||||
}
|
||||
}
|
@@ -1,45 +1,10 @@
|
||||
use crate::syntax::{config::Configuration, Loader, LoaderError};
|
||||
|
||||
/// Language configuration based on built-in languages.toml.
|
||||
pub fn default_lang_config() -> Configuration {
|
||||
helix_loader::config::default_lang_config()
|
||||
/// Syntax configuration loader based on built-in languages.toml.
|
||||
pub fn default_syntax_loader() -> crate::syntax::Configuration {
|
||||
helix_loader::default_lang_config()
|
||||
.try_into()
|
||||
.expect("Could not deserialize built-in languages.toml")
|
||||
.expect("Could not serialize built-in languages.toml")
|
||||
}
|
||||
|
||||
/// Language configuration loader based on built-in languages.toml.
|
||||
pub fn default_lang_loader() -> Loader {
|
||||
Loader::new(default_lang_config()).expect("Could not compile loader for default config")
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LanguageLoaderError {
|
||||
DeserializeError(toml::de::Error),
|
||||
LoaderError(LoaderError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LanguageLoaderError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::DeserializeError(err) => write!(f, "Failed to parse language config: {err}"),
|
||||
Self::LoaderError(err) => write!(f, "Failed to compile language config: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LanguageLoaderError {}
|
||||
|
||||
/// Language configuration based on user configured languages.toml.
|
||||
pub fn user_lang_config() -> Result<Configuration, toml::de::Error> {
|
||||
helix_loader::config::user_lang_config()?.try_into()
|
||||
}
|
||||
|
||||
/// Language configuration loader based on user configured languages.toml.
|
||||
pub fn user_lang_loader() -> Result<Loader, LanguageLoaderError> {
|
||||
let config: Configuration = helix_loader::config::user_lang_config()
|
||||
.map_err(LanguageLoaderError::DeserializeError)?
|
||||
.try_into()
|
||||
.map_err(LanguageLoaderError::DeserializeError)?;
|
||||
|
||||
Loader::new(config).map_err(LanguageLoaderError::LoaderError)
|
||||
/// Syntax configuration loader based on user configured languages.toml.
|
||||
pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
|
||||
helix_loader::user_lang_config()?.try_into()
|
||||
}
|
||||
|
@@ -1,12 +1,8 @@
|
||||
//! LSP diagnostic utility types.
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
pub use helix_stdx::range::Range;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Describes the severity level of a [`Diagnostic`].
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
pub enum Severity {
|
||||
Hint,
|
||||
Info,
|
||||
@@ -20,83 +16,18 @@ impl Default for Severity {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub enum NumberOrString {
|
||||
Number(i32),
|
||||
String(String),
|
||||
/// A range of `char`s within the text.
|
||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct Range {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DiagnosticTag {
|
||||
Unnecessary,
|
||||
Deprecated,
|
||||
}
|
||||
|
||||
/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.94.0/lsp_types/struct.Diagnostic.html)
|
||||
/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Diagnostic {
|
||||
pub range: Range,
|
||||
// whether this diagnostic ends at the end of(or inside) a word
|
||||
pub ends_at_word: bool,
|
||||
pub starts_at_word: bool,
|
||||
pub zero_width: bool,
|
||||
pub line: usize,
|
||||
pub message: String,
|
||||
pub severity: Option<Severity>,
|
||||
pub code: Option<NumberOrString>,
|
||||
pub provider: DiagnosticProvider,
|
||||
pub tags: Vec<DiagnosticTag>,
|
||||
pub source: Option<String>,
|
||||
pub data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// The source of a diagnostic.
|
||||
///
|
||||
/// This type is cheap to clone: all data is either `Copy` or wrapped in an `Arc`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum DiagnosticProvider {
|
||||
Lsp {
|
||||
/// The ID of the language server which sent the diagnostic.
|
||||
server_id: LanguageServerId,
|
||||
/// An optional identifier under which diagnostics are managed by the client.
|
||||
///
|
||||
/// `identifier` is a field from the LSP "Pull Diagnostics" feature meant to provide an
|
||||
/// optional "namespace" for diagnostics: a language server can respond to a diagnostics
|
||||
/// pull request with an identifier and these diagnostics should be treated as separate
|
||||
/// from push diagnostics. Rust-analyzer uses this feature for example to provide Cargo
|
||||
/// diagnostics with push and internal diagnostics with pull. The push diagnostics should
|
||||
/// not clear the pull diagnostics and vice-versa.
|
||||
identifier: Option<Arc<str>>,
|
||||
},
|
||||
// Future internal features can go here...
|
||||
}
|
||||
|
||||
impl DiagnosticProvider {
|
||||
pub fn language_server_id(&self) -> Option<LanguageServerId> {
|
||||
match self {
|
||||
Self::Lsp { server_id, .. } => Some(*server_id),
|
||||
// _ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// while I would prefer having this in helix-lsp that necessitates a bunch of
|
||||
// conversions I would rather not add. I think its fine since this just a very
|
||||
// trivial newtype wrapper and we would need something similar once we define
|
||||
// completions in core
|
||||
slotmap::new_key_type! {
|
||||
pub struct LanguageServerId;
|
||||
}
|
||||
|
||||
impl fmt::Display for LanguageServerId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
#[inline]
|
||||
pub fn severity(&self) -> Severity {
|
||||
self.severity.unwrap_or(Severity::Warning)
|
||||
}
|
||||
}
|
||||
|
@@ -1,184 +1,58 @@
|
||||
use std::ops::Range;
|
||||
use std::time::Instant;
|
||||
|
||||
use imara_diff::{Algorithm, Diff, Hunk, IndentHeuristic, IndentLevel, InternedInput};
|
||||
use ropey::RopeSlice;
|
||||
|
||||
use crate::{ChangeSet, Rope, Tendril, Transaction};
|
||||
|
||||
struct ChangeSetBuilder<'a> {
|
||||
res: ChangeSet,
|
||||
after: RopeSlice<'a>,
|
||||
file: &'a InternedInput<RopeSlice<'a>>,
|
||||
current_hunk: InternedInput<char>,
|
||||
char_diff: Diff,
|
||||
pos: u32,
|
||||
}
|
||||
|
||||
impl ChangeSetBuilder<'_> {
|
||||
fn process_hunk(&mut self, before: Range<u32>, after: Range<u32>) {
|
||||
let len = self.file.before[self.pos as usize..before.start as usize]
|
||||
.iter()
|
||||
.map(|&it| self.file.interner[it].len_chars())
|
||||
.sum();
|
||||
self.res.retain(len);
|
||||
self.pos = before.end;
|
||||
|
||||
// do not perform diffs on large hunks
|
||||
let len_before = before.end - before.start;
|
||||
let len_after = after.end - after.start;
|
||||
|
||||
// Pure insertions/removals do not require a character diff.
|
||||
// Very large changes are ignored because their character diff is expensive to compute
|
||||
// TODO adjust heuristic to detect large changes?
|
||||
if len_before == 0
|
||||
|| len_after == 0
|
||||
|| len_after > 5 * len_before
|
||||
|| 5 * len_after < len_before && len_before > 10
|
||||
|| len_before + len_after > 200
|
||||
{
|
||||
let remove = self.file.before[before.start as usize..before.end as usize]
|
||||
.iter()
|
||||
.map(|&it| self.file.interner[it].len_chars())
|
||||
.sum();
|
||||
self.res.delete(remove);
|
||||
let mut fragment = Tendril::new();
|
||||
if len_after > 500 {
|
||||
// copying a rope line by line is slower then copying the entire
|
||||
// rope. Use to_string for very large changes instead..
|
||||
if self.file.after.len() == after.end as usize {
|
||||
if after.start == 0 {
|
||||
fragment = self.after.to_string().into();
|
||||
} else {
|
||||
let start = self.after.line_to_char(after.start as usize);
|
||||
fragment = self.after.slice(start..).to_string().into();
|
||||
}
|
||||
} else if after.start == 0 {
|
||||
let end = self.after.line_to_char(after.end as usize);
|
||||
fragment = self.after.slice(..end).to_string().into();
|
||||
} else {
|
||||
let start = self.after.line_to_char(after.start as usize);
|
||||
let end = self.after.line_to_char(after.end as usize);
|
||||
fragment = self.after.slice(start..end).to_string().into();
|
||||
}
|
||||
} else {
|
||||
for &line in &self.file.after[after.start as usize..after.end as usize] {
|
||||
for chunk in self.file.interner[line].chunks() {
|
||||
fragment.push_str(chunk)
|
||||
}
|
||||
}
|
||||
};
|
||||
self.res.insert(fragment);
|
||||
} else {
|
||||
// for reasonably small hunks, generating a ChangeSet from char diff can save memory
|
||||
// TODO use a tokenizer (word diff?) for improved performance
|
||||
let hunk_before = self.file.before[before.start as usize..before.end as usize]
|
||||
.iter()
|
||||
.flat_map(|&it| self.file.interner[it].chars());
|
||||
let hunk_after = self.file.after[after.start as usize..after.end as usize]
|
||||
.iter()
|
||||
.flat_map(|&it| self.file.interner[it].chars());
|
||||
self.current_hunk.update_before(hunk_before);
|
||||
self.current_hunk.update_after(hunk_after);
|
||||
// the histogram heuristic does not work as well
|
||||
// for characters because the same characters often reoccur
|
||||
// use myer diff instead
|
||||
self.char_diff.compute_with(
|
||||
Algorithm::Myers,
|
||||
&self.current_hunk.before,
|
||||
&self.current_hunk.after,
|
||||
self.current_hunk.interner.num_tokens(),
|
||||
);
|
||||
let mut pos = 0;
|
||||
for Hunk { before, after } in self.char_diff.hunks() {
|
||||
self.res.retain((before.start - pos) as usize);
|
||||
self.res.delete(before.len());
|
||||
pos = before.end;
|
||||
|
||||
let res = self.current_hunk.after[after.start as usize..after.end as usize]
|
||||
.iter()
|
||||
.map(|&token| self.current_hunk.interner[token])
|
||||
.collect();
|
||||
|
||||
self.res.insert(res);
|
||||
}
|
||||
self.res
|
||||
.retain(self.current_hunk.before.len() - pos as usize);
|
||||
// reuse allocations
|
||||
self.current_hunk.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(mut self) -> ChangeSet {
|
||||
let len = self.file.before[self.pos as usize..]
|
||||
.iter()
|
||||
.map(|&it| self.file.interner[it].len_chars())
|
||||
.sum();
|
||||
|
||||
self.res.retain(len);
|
||||
self.res
|
||||
}
|
||||
}
|
||||
|
||||
struct RopeLines<'a>(RopeSlice<'a>);
|
||||
|
||||
impl<'a> imara_diff::TokenSource for RopeLines<'a> {
|
||||
type Token = RopeSlice<'a>;
|
||||
type Tokenizer = ropey::iter::Lines<'a>;
|
||||
|
||||
fn tokenize(&self) -> Self::Tokenizer {
|
||||
self.0.lines()
|
||||
}
|
||||
|
||||
fn estimate_tokens(&self) -> u32 {
|
||||
// we can provide a perfect estimate which is very nice for performance
|
||||
self.0.len_lines() as u32
|
||||
}
|
||||
}
|
||||
use crate::{Rope, Transaction};
|
||||
|
||||
/// Compares `old` and `new` to generate a [`Transaction`] describing
|
||||
/// the steps required to get from `old` to `new`.
|
||||
pub fn compare_ropes(before: &Rope, after: &Rope) -> Transaction {
|
||||
let start = Instant::now();
|
||||
let res = ChangeSet::with_capacity(32);
|
||||
let after = after.slice(..);
|
||||
let file = InternedInput::new(RopeLines(before.slice(..)), RopeLines(after));
|
||||
let mut builder = ChangeSetBuilder {
|
||||
res,
|
||||
file: &file,
|
||||
after,
|
||||
pos: 0,
|
||||
current_hunk: InternedInput::default(),
|
||||
char_diff: Diff::default(),
|
||||
};
|
||||
let mut diff = Diff::compute(Algorithm::Histogram, &file);
|
||||
diff.postprocess_with_heuristic(
|
||||
&file,
|
||||
IndentHeuristic::new(|token| IndentLevel::for_ascii_line(file.interner[token].bytes(), 4)),
|
||||
);
|
||||
for hunk in diff.hunks() {
|
||||
builder.process_hunk(hunk.before, hunk.after)
|
||||
}
|
||||
let res = builder.finish().into();
|
||||
pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction {
|
||||
// `similar` only works on contiguous data, so a `Rope` has
|
||||
// to be temporarily converted into a `String`.
|
||||
let old_converted = old.to_string();
|
||||
let new_converted = new.to_string();
|
||||
|
||||
log::debug!(
|
||||
"rope diff took {}s",
|
||||
Instant::now().duration_since(start).as_secs_f64()
|
||||
);
|
||||
res
|
||||
// A timeout is set so after 1 seconds, the algorithm will start
|
||||
// approximating. This is especially important for big `Rope`s or
|
||||
// `Rope`s that are extremely dissimilar to each other.
|
||||
let mut config = similar::TextDiff::configure();
|
||||
config.timeout(std::time::Duration::from_secs(1));
|
||||
|
||||
let diff = config.diff_chars(&old_converted, &new_converted);
|
||||
|
||||
// The current position of the change needs to be tracked to
|
||||
// construct the `Change`s.
|
||||
let mut pos = 0;
|
||||
Transaction::change(
|
||||
old,
|
||||
diff.ops()
|
||||
.iter()
|
||||
.map(|op| op.as_tag_tuple())
|
||||
.filter_map(|(tag, old_range, new_range)| {
|
||||
// `old_pos..pos` is equivalent to `start..end` for where
|
||||
// the change should be applied.
|
||||
let old_pos = pos;
|
||||
pos += old_range.end - old_range.start;
|
||||
|
||||
match tag {
|
||||
// Semantically, inserts and replacements are the same thing.
|
||||
similar::DiffTag::Insert | similar::DiffTag::Replace => {
|
||||
// This is the text from the `new` rope that should be
|
||||
// inserted into `old`.
|
||||
let text: &str = {
|
||||
let start = new.char_to_byte(new_range.start);
|
||||
let end = new.char_to_byte(new_range.end);
|
||||
&new_converted[start..end]
|
||||
};
|
||||
Some((old_pos, pos, Some(text.into())))
|
||||
}
|
||||
similar::DiffTag::Delete => Some((old_pos, pos, None)),
|
||||
similar::DiffTag::Equal => None,
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn test_identity(a: &str, b: &str) {
|
||||
let mut old = Rope::from(a);
|
||||
let new = Rope::from(b);
|
||||
compare_ropes(&old, &new).apply(&mut old);
|
||||
assert_eq!(old, new);
|
||||
}
|
||||
|
||||
quickcheck::quickcheck! {
|
||||
fn test_compare_ropes(a: String, b: String) -> bool {
|
||||
let mut old = Rope::from(a);
|
||||
@@ -187,25 +61,4 @@ mod tests {
|
||||
old == new
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal_files() {
|
||||
test_identity("foo", "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_newline() {
|
||||
test_identity("foo\n", "foo");
|
||||
test_identity("foo", "foo\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_file() {
|
||||
test_identity("", "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleted_file() {
|
||||
test_identity("foo", "");
|
||||
}
|
||||
}
|
||||
|
@@ -1,480 +0,0 @@
|
||||
//! The `DocumentFormatter` forms the bridge between the raw document text
|
||||
//! and onscreen positioning. It yields the text graphemes as an iterator
|
||||
//! and traverses (part) of the document text. During that traversal it
|
||||
//! handles grapheme detection, softwrapping and annotations.
|
||||
//! It yields `FormattedGrapheme`s and their corresponding visual coordinates.
|
||||
//!
|
||||
//! As both virtual text and softwrapping can insert additional lines into the document
|
||||
//! it is generally not possible to find the start of the previous visual line.
|
||||
//! Instead the `DocumentFormatter` starts at the last "checkpoint" (usually a linebreak)
|
||||
//! called a "block" and the caller must advance it as needed.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::mem::replace;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use unicode_segmentation::{Graphemes, UnicodeSegmentation};
|
||||
|
||||
use helix_stdx::rope::{RopeGraphemes, RopeSliceExt};
|
||||
|
||||
use crate::graphemes::{Grapheme, GraphemeStr};
|
||||
use crate::syntax::Highlight;
|
||||
use crate::text_annotations::TextAnnotations;
|
||||
use crate::{Position, RopeSlice};
|
||||
|
||||
/// TODO make Highlight a u32 to reduce the size of this enum to a single word.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GraphemeSource {
|
||||
Document {
|
||||
codepoints: u32,
|
||||
},
|
||||
/// Inline virtual text can not be highlighted with a `Highlight` iterator
|
||||
/// because it's not part of the document. Instead the `Highlight`
|
||||
/// is emitted right by the document formatter
|
||||
VirtualText {
|
||||
highlight: Option<Highlight>,
|
||||
},
|
||||
}
|
||||
|
||||
impl GraphemeSource {
|
||||
/// Returns whether this grapheme is virtual inline text
|
||||
pub fn is_virtual(self) -> bool {
|
||||
matches!(self, GraphemeSource::VirtualText { .. })
|
||||
}
|
||||
|
||||
pub fn is_eof(self) -> bool {
|
||||
// all doc chars except the EOF char have non-zero codepoints
|
||||
matches!(self, GraphemeSource::Document { codepoints: 0 })
|
||||
}
|
||||
|
||||
pub fn doc_chars(self) -> usize {
|
||||
match self {
|
||||
GraphemeSource::Document { codepoints } => codepoints as usize,
|
||||
GraphemeSource::VirtualText { .. } => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FormattedGrapheme<'a> {
|
||||
pub raw: Grapheme<'a>,
|
||||
pub source: GraphemeSource,
|
||||
pub visual_pos: Position,
|
||||
/// Document line at the start of the grapheme
|
||||
pub line_idx: usize,
|
||||
/// Document char position at the start of the grapheme
|
||||
pub char_idx: usize,
|
||||
}
|
||||
|
||||
impl FormattedGrapheme<'_> {
|
||||
pub fn is_virtual(&self) -> bool {
|
||||
self.source.is_virtual()
|
||||
}
|
||||
|
||||
pub fn doc_chars(&self) -> usize {
|
||||
self.source.doc_chars()
|
||||
}
|
||||
|
||||
pub fn is_whitespace(&self) -> bool {
|
||||
self.raw.is_whitespace()
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.raw.width()
|
||||
}
|
||||
|
||||
pub fn is_word_boundary(&self) -> bool {
|
||||
self.raw.is_word_boundary()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct GraphemeWithSource<'a> {
|
||||
grapheme: Grapheme<'a>,
|
||||
source: GraphemeSource,
|
||||
}
|
||||
|
||||
impl<'a> GraphemeWithSource<'a> {
|
||||
fn new(
|
||||
g: GraphemeStr<'a>,
|
||||
visual_x: usize,
|
||||
tab_width: u16,
|
||||
source: GraphemeSource,
|
||||
) -> GraphemeWithSource<'a> {
|
||||
GraphemeWithSource {
|
||||
grapheme: Grapheme::new(g, visual_x, tab_width),
|
||||
source,
|
||||
}
|
||||
}
|
||||
fn placeholder() -> Self {
|
||||
GraphemeWithSource {
|
||||
grapheme: Grapheme::Other { g: " ".into() },
|
||||
source: GraphemeSource::Document { codepoints: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
fn doc_chars(&self) -> usize {
|
||||
self.source.doc_chars()
|
||||
}
|
||||
|
||||
fn is_whitespace(&self) -> bool {
|
||||
self.grapheme.is_whitespace()
|
||||
}
|
||||
|
||||
fn is_newline(&self) -> bool {
|
||||
matches!(self.grapheme, Grapheme::Newline)
|
||||
}
|
||||
|
||||
fn is_eof(&self) -> bool {
|
||||
self.source.is_eof()
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
self.grapheme.width()
|
||||
}
|
||||
|
||||
fn is_word_boundary(&self) -> bool {
|
||||
self.grapheme.is_word_boundary()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextFormat {
|
||||
pub soft_wrap: bool,
|
||||
pub tab_width: u16,
|
||||
pub max_wrap: u16,
|
||||
pub max_indent_retain: u16,
|
||||
pub wrap_indicator: Box<str>,
|
||||
pub wrap_indicator_highlight: Option<Highlight>,
|
||||
pub viewport_width: u16,
|
||||
pub soft_wrap_at_text_width: bool,
|
||||
}
|
||||
|
||||
// test implementation is basically only used for testing or when softwrap is always disabled
|
||||
impl Default for TextFormat {
|
||||
fn default() -> Self {
|
||||
TextFormat {
|
||||
soft_wrap: false,
|
||||
tab_width: 4,
|
||||
max_wrap: 3,
|
||||
max_indent_retain: 4,
|
||||
wrap_indicator: Box::from(" "),
|
||||
viewport_width: 17,
|
||||
wrap_indicator_highlight: None,
|
||||
soft_wrap_at_text_width: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DocumentFormatter<'t> {
|
||||
text_fmt: &'t TextFormat,
|
||||
annotations: &'t TextAnnotations<'t>,
|
||||
|
||||
/// The visual position at the end of the last yielded word boundary
|
||||
visual_pos: Position,
|
||||
graphemes: RopeGraphemes<'t>,
|
||||
/// The character pos of the `graphemes` iter used for inserting annotations
|
||||
char_pos: usize,
|
||||
/// The line pos of the `graphemes` iter used for inserting annotations
|
||||
line_pos: usize,
|
||||
exhausted: bool,
|
||||
|
||||
inline_annotation_graphemes: Option<(Graphemes<'t>, Option<Highlight>)>,
|
||||
|
||||
// softwrap specific
|
||||
/// The indentation of the current line
|
||||
/// Is set to `None` if the indentation level is not yet known
|
||||
/// because no non-whitespace graphemes have been encountered yet
|
||||
indent_level: Option<usize>,
|
||||
/// In case a long word needs to be split a single grapheme might need to be wrapped
|
||||
/// while the rest of the word stays on the same line
|
||||
peeked_grapheme: Option<GraphemeWithSource<'t>>,
|
||||
/// A first-in first-out (fifo) buffer for the Graphemes of any given word
|
||||
word_buf: Vec<GraphemeWithSource<'t>>,
|
||||
/// The index of the next grapheme that will be yielded from the `word_buf`
|
||||
word_i: usize,
|
||||
}
|
||||
|
||||
impl<'t> DocumentFormatter<'t> {
|
||||
/// Creates a new formatter at the last block before `char_idx`.
|
||||
/// A block is a chunk which always ends with a linebreak.
|
||||
/// This is usually just a normal line break.
|
||||
/// However very long lines are always wrapped at constant intervals that can be cheaply calculated
|
||||
/// to avoid pathological behaviour.
|
||||
pub fn new_at_prev_checkpoint(
|
||||
text: RopeSlice<'t>,
|
||||
text_fmt: &'t TextFormat,
|
||||
annotations: &'t TextAnnotations,
|
||||
char_idx: usize,
|
||||
) -> Self {
|
||||
// TODO divide long lines into blocks to avoid bad performance for long lines
|
||||
let block_line_idx = text.char_to_line(char_idx.min(text.len_chars()));
|
||||
let block_char_idx = text.line_to_char(block_line_idx);
|
||||
annotations.reset_pos(block_char_idx);
|
||||
|
||||
DocumentFormatter {
|
||||
text_fmt,
|
||||
annotations,
|
||||
visual_pos: Position { row: 0, col: 0 },
|
||||
graphemes: text.slice(block_char_idx..).graphemes(),
|
||||
char_pos: block_char_idx,
|
||||
exhausted: false,
|
||||
indent_level: None,
|
||||
peeked_grapheme: None,
|
||||
word_buf: Vec::with_capacity(64),
|
||||
word_i: 0,
|
||||
line_pos: block_line_idx,
|
||||
inline_annotation_graphemes: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_inline_annotation_grapheme(
|
||||
&mut self,
|
||||
char_pos: usize,
|
||||
) -> Option<(&'t str, Option<Highlight>)> {
|
||||
loop {
|
||||
if let Some(&mut (ref mut annotation, highlight)) =
|
||||
self.inline_annotation_graphemes.as_mut()
|
||||
{
|
||||
if let Some(grapheme) = annotation.next() {
|
||||
return Some((grapheme, highlight));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((annotation, highlight)) =
|
||||
self.annotations.next_inline_annotation_at(char_pos)
|
||||
{
|
||||
self.inline_annotation_graphemes = Some((
|
||||
UnicodeSegmentation::graphemes(&*annotation.text, true),
|
||||
highlight,
|
||||
))
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_grapheme(&mut self, col: usize, char_pos: usize) -> Option<GraphemeWithSource<'t>> {
|
||||
let (grapheme, source) =
|
||||
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme(char_pos) {
|
||||
(grapheme.into(), GraphemeSource::VirtualText { highlight })
|
||||
} else if let Some(grapheme) = self.graphemes.next() {
|
||||
let codepoints = grapheme.len_chars() as u32;
|
||||
|
||||
let overlay = self.annotations.overlay_at(char_pos);
|
||||
let grapheme = match overlay {
|
||||
Some((overlay, _)) => overlay.grapheme.as_str().into(),
|
||||
None => Cow::from(grapheme).into(),
|
||||
};
|
||||
|
||||
(grapheme, GraphemeSource::Document { codepoints })
|
||||
} else {
|
||||
if self.exhausted {
|
||||
return None;
|
||||
}
|
||||
self.exhausted = true;
|
||||
// EOF grapheme is required for rendering
|
||||
// and correct position computations
|
||||
return Some(GraphemeWithSource {
|
||||
grapheme: Grapheme::Other { g: " ".into() },
|
||||
source: GraphemeSource::Document { codepoints: 0 },
|
||||
});
|
||||
};
|
||||
|
||||
let grapheme = GraphemeWithSource::new(grapheme, col, self.text_fmt.tab_width, source);
|
||||
|
||||
Some(grapheme)
|
||||
}
|
||||
|
||||
/// Move a word to the next visual line
|
||||
fn wrap_word(&mut self) -> usize {
|
||||
// softwrap this word to the next line
|
||||
let indent_carry_over = if let Some(indent) = self.indent_level {
|
||||
if indent as u16 <= self.text_fmt.max_indent_retain {
|
||||
indent as u16
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
// ensure the indent stays 0
|
||||
self.indent_level = Some(0);
|
||||
0
|
||||
};
|
||||
|
||||
let virtual_lines =
|
||||
self.annotations
|
||||
.virtual_lines_at(self.char_pos, self.visual_pos, self.line_pos);
|
||||
self.visual_pos.col = indent_carry_over as usize;
|
||||
self.visual_pos.row += 1 + virtual_lines;
|
||||
let mut i = 0;
|
||||
let mut word_width = 0;
|
||||
let wrap_indicator = UnicodeSegmentation::graphemes(&*self.text_fmt.wrap_indicator, true)
|
||||
.map(|g| {
|
||||
i += 1;
|
||||
let grapheme = GraphemeWithSource::new(
|
||||
g.into(),
|
||||
self.visual_pos.col + word_width,
|
||||
self.text_fmt.tab_width,
|
||||
GraphemeSource::VirtualText {
|
||||
highlight: self.text_fmt.wrap_indicator_highlight,
|
||||
},
|
||||
);
|
||||
word_width += grapheme.width();
|
||||
grapheme
|
||||
});
|
||||
self.word_buf.splice(0..0, wrap_indicator);
|
||||
|
||||
for grapheme in &mut self.word_buf[i..] {
|
||||
let visual_x = self.visual_pos.col + word_width;
|
||||
grapheme
|
||||
.grapheme
|
||||
.change_position(visual_x, self.text_fmt.tab_width);
|
||||
word_width += grapheme.width();
|
||||
}
|
||||
if let Some(grapheme) = &mut self.peeked_grapheme {
|
||||
let visual_x = self.visual_pos.col + word_width;
|
||||
grapheme
|
||||
.grapheme
|
||||
.change_position(visual_x, self.text_fmt.tab_width);
|
||||
}
|
||||
word_width
|
||||
}
|
||||
|
||||
fn peek_grapheme(&mut self, col: usize, char_pos: usize) -> Option<&GraphemeWithSource<'t>> {
|
||||
if self.peeked_grapheme.is_none() {
|
||||
self.peeked_grapheme = self.advance_grapheme(col, char_pos);
|
||||
}
|
||||
self.peeked_grapheme.as_ref()
|
||||
}
|
||||
|
||||
fn next_grapheme(&mut self, col: usize, char_pos: usize) -> Option<GraphemeWithSource<'t>> {
|
||||
self.peek_grapheme(col, char_pos);
|
||||
self.peeked_grapheme.take()
|
||||
}
|
||||
|
||||
fn advance_to_next_word(&mut self) {
|
||||
self.word_buf.clear();
|
||||
let mut word_width = 0;
|
||||
let mut word_chars = 0;
|
||||
|
||||
if self.exhausted {
|
||||
return;
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut col = self.visual_pos.col + word_width;
|
||||
let char_pos = self.char_pos + word_chars;
|
||||
match col.cmp(&(self.text_fmt.viewport_width as usize)) {
|
||||
// The EOF char and newline chars are always selectable in helix. That means
|
||||
// that wrapping happens "too-early" if a word fits a line perfectly. This
|
||||
// is intentional so that all selectable graphemes are always visible (and
|
||||
// therefore the cursor never disappears). However if the user manually set a
|
||||
// lower softwrap width then this is undesirable. Just increasing the viewport-
|
||||
// width by one doesn't work because if a line is wrapped multiple times then
|
||||
// some words may extend past the specified width.
|
||||
//
|
||||
// So we special case a word that ends exactly at line bounds and is followed
|
||||
// by a newline/eof character here.
|
||||
Ordering::Equal
|
||||
if self.text_fmt.soft_wrap_at_text_width
|
||||
&& self
|
||||
.peek_grapheme(col, char_pos)
|
||||
.is_some_and(|grapheme| grapheme.is_newline() || grapheme.is_eof()) => {
|
||||
}
|
||||
Ordering::Equal if word_width > self.text_fmt.max_wrap as usize => return,
|
||||
Ordering::Greater if word_width > self.text_fmt.max_wrap as usize => {
|
||||
self.peeked_grapheme = self.word_buf.pop();
|
||||
return;
|
||||
}
|
||||
Ordering::Equal | Ordering::Greater => {
|
||||
word_width = self.wrap_word();
|
||||
col = self.visual_pos.col + word_width;
|
||||
}
|
||||
Ordering::Less => (),
|
||||
}
|
||||
|
||||
let Some(grapheme) = self.next_grapheme(col, char_pos) else {
|
||||
return;
|
||||
};
|
||||
word_chars += grapheme.doc_chars();
|
||||
|
||||
// Track indentation
|
||||
if !grapheme.is_whitespace() && self.indent_level.is_none() {
|
||||
self.indent_level = Some(self.visual_pos.col);
|
||||
} else if grapheme.grapheme == Grapheme::Newline {
|
||||
self.indent_level = None;
|
||||
}
|
||||
|
||||
let is_word_boundary = grapheme.is_word_boundary();
|
||||
word_width += grapheme.width();
|
||||
self.word_buf.push(grapheme);
|
||||
|
||||
if is_word_boundary {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the char index at the end of the last yielded grapheme
|
||||
pub fn next_char_pos(&self) -> usize {
|
||||
self.char_pos
|
||||
}
|
||||
/// returns the visual position at the end of the last yielded grapheme
|
||||
pub fn next_visual_pos(&self) -> Position {
|
||||
self.visual_pos
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> Iterator for DocumentFormatter<'t> {
|
||||
type Item = FormattedGrapheme<'t>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let grapheme = if self.text_fmt.soft_wrap {
|
||||
if self.word_i >= self.word_buf.len() {
|
||||
self.advance_to_next_word();
|
||||
self.word_i = 0;
|
||||
}
|
||||
let grapheme = replace(
|
||||
self.word_buf.get_mut(self.word_i)?,
|
||||
GraphemeWithSource::placeholder(),
|
||||
);
|
||||
self.word_i += 1;
|
||||
grapheme
|
||||
} else {
|
||||
self.advance_grapheme(self.visual_pos.col, self.char_pos)?
|
||||
};
|
||||
|
||||
let grapheme = FormattedGrapheme {
|
||||
raw: grapheme.grapheme,
|
||||
source: grapheme.source,
|
||||
visual_pos: self.visual_pos,
|
||||
line_idx: self.line_pos,
|
||||
char_idx: self.char_pos,
|
||||
};
|
||||
|
||||
self.char_pos += grapheme.doc_chars();
|
||||
if !grapheme.is_virtual() {
|
||||
self.annotations.process_virtual_text_anchors(&grapheme);
|
||||
}
|
||||
if grapheme.raw == Grapheme::Newline {
|
||||
// move to end of newline char
|
||||
self.visual_pos.col += 1;
|
||||
let virtual_lines =
|
||||
self.annotations
|
||||
.virtual_lines_at(self.char_pos, self.visual_pos, self.line_pos);
|
||||
self.visual_pos.row += 1 + virtual_lines;
|
||||
self.visual_pos.col = 0;
|
||||
if !grapheme.is_virtual() {
|
||||
self.line_pos += 1;
|
||||
}
|
||||
} else {
|
||||
self.visual_pos.col += grapheme.width();
|
||||
}
|
||||
Some(grapheme)
|
||||
}
|
||||
}
|
@@ -1,213 +0,0 @@
|
||||
use crate::doc_formatter::{DocumentFormatter, TextFormat};
|
||||
use crate::text_annotations::{InlineAnnotation, Overlay, TextAnnotations};
|
||||
|
||||
impl TextFormat {
|
||||
fn new_test(softwrap: bool) -> Self {
|
||||
TextFormat {
|
||||
soft_wrap: softwrap,
|
||||
tab_width: 2,
|
||||
max_wrap: 3,
|
||||
max_indent_retain: 4,
|
||||
wrap_indicator: ".".into(),
|
||||
wrap_indicator_highlight: None,
|
||||
// use a prime number to allow lining up too often with repeat
|
||||
viewport_width: 17,
|
||||
soft_wrap_at_text_width: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> DocumentFormatter<'t> {
|
||||
fn collect_to_str(&mut self) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut res = String::new();
|
||||
let viewport_width = self.text_fmt.viewport_width;
|
||||
let soft_wrap_at_text_width = self.text_fmt.soft_wrap_at_text_width;
|
||||
let mut line = 0;
|
||||
|
||||
for grapheme in self {
|
||||
if grapheme.visual_pos.row != line {
|
||||
line += 1;
|
||||
assert_eq!(grapheme.visual_pos.row, line);
|
||||
write!(res, "\n{}", ".".repeat(grapheme.visual_pos.col)).unwrap();
|
||||
}
|
||||
if !soft_wrap_at_text_width {
|
||||
assert!(
|
||||
grapheme.visual_pos.col <= viewport_width as usize,
|
||||
"softwrapped failed {}<={viewport_width}",
|
||||
grapheme.visual_pos.col
|
||||
);
|
||||
}
|
||||
write!(res, "{}", grapheme.raw).unwrap();
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
fn softwrap_text(text: &str) -> String {
|
||||
DocumentFormatter::new_at_prev_checkpoint(
|
||||
text.into(),
|
||||
&TextFormat::new_test(true),
|
||||
&TextAnnotations::default(),
|
||||
0,
|
||||
)
|
||||
.collect_to_str()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_softwrap() {
|
||||
assert_eq!(
|
||||
softwrap_text(&"foo ".repeat(10)),
|
||||
"foo foo foo foo \n.foo foo foo foo \n.foo foo "
|
||||
);
|
||||
assert_eq!(
|
||||
softwrap_text(&"fooo ".repeat(10)),
|
||||
"fooo fooo fooo \n.fooo fooo fooo \n.fooo fooo fooo \n.fooo "
|
||||
);
|
||||
|
||||
// check that we don't wrap unnecessarily
|
||||
assert_eq!(softwrap_text("\t\txxxx1xxxx2xx\n"), " xxxx1xxxx2xx \n ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn softwrap_indentation() {
|
||||
assert_eq!(
|
||||
softwrap_text("\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
|
||||
" foo1 foo2 \n.....foo3 foo4 \n.....foo5 foo6 \n "
|
||||
);
|
||||
assert_eq!(
|
||||
softwrap_text("\t\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
|
||||
" foo1 foo2 \n.foo3 foo4 foo5 \n.foo6 \n "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_word_softwrap() {
|
||||
assert_eq!(
|
||||
softwrap_text("\t\txxxx1xxxx2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
|
||||
" xxxx1xxxx2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
|
||||
);
|
||||
assert_eq!(
|
||||
softwrap_text("xxxxxxxx1xxxx2xxx\n"),
|
||||
"xxxxxxxx1xxxx2xxx\n. \n "
|
||||
);
|
||||
assert_eq!(
|
||||
softwrap_text("\t\txxxx1xxxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
|
||||
" xxxx1xxxx \n.....2xxxx3xxxx4x\n.....xxx5xxxx6xxx\n.....x7xxxx8xxxx9\n.....xxx \n "
|
||||
);
|
||||
assert_eq!(
|
||||
softwrap_text("\t\txxxx1xxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
|
||||
" xxxx1xxx 2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn softwrap_multichar_grapheme() {
|
||||
assert_eq!(
|
||||
softwrap_text("xxxx xxxx xxx a\u{0301}bc\n"),
|
||||
"xxxx xxxx xxx \n.ábc \n "
|
||||
)
|
||||
}
|
||||
|
||||
fn softwrap_text_at_text_width(text: &str) -> String {
|
||||
let mut text_fmt = TextFormat::new_test(true);
|
||||
text_fmt.soft_wrap_at_text_width = true;
|
||||
let annotations = TextAnnotations::default();
|
||||
let mut formatter =
|
||||
DocumentFormatter::new_at_prev_checkpoint(text.into(), &text_fmt, &annotations, 0);
|
||||
formatter.collect_to_str()
|
||||
}
|
||||
#[test]
|
||||
fn long_word_softwrap_text_width() {
|
||||
assert_eq!(
|
||||
softwrap_text_at_text_width("xxxxxxxx1xxxx2xxx\nxxxxxxxx1xxxx2xxx"),
|
||||
"xxxxxxxx1xxxx2xxx \nxxxxxxxx1xxxx2xxx "
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay_text(text: &str, char_pos: usize, softwrap: bool, overlays: &[Overlay]) -> String {
|
||||
DocumentFormatter::new_at_prev_checkpoint(
|
||||
text.into(),
|
||||
&TextFormat::new_test(softwrap),
|
||||
TextAnnotations::default().add_overlay(overlays, None),
|
||||
char_pos,
|
||||
)
|
||||
.collect_to_str()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay() {
|
||||
assert_eq!(
|
||||
overlay_text(
|
||||
"foobar",
|
||||
0,
|
||||
false,
|
||||
&[Overlay::new(0, "X"), Overlay::new(2, "\t")],
|
||||
),
|
||||
"Xo bar "
|
||||
);
|
||||
assert_eq!(
|
||||
overlay_text(
|
||||
&"foo ".repeat(10),
|
||||
0,
|
||||
true,
|
||||
&[
|
||||
Overlay::new(2, "\t"),
|
||||
Overlay::new(5, "\t"),
|
||||
Overlay::new(16, "X"),
|
||||
]
|
||||
),
|
||||
"fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
|
||||
);
|
||||
}
|
||||
|
||||
fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -> String {
|
||||
DocumentFormatter::new_at_prev_checkpoint(
|
||||
text.into(),
|
||||
&TextFormat::new_test(softwrap),
|
||||
TextAnnotations::default().add_inline_annotations(annotations, None),
|
||||
0,
|
||||
)
|
||||
.collect_to_str()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotation() {
|
||||
assert_eq!(
|
||||
annotate_text("bar", false, &[InlineAnnotation::new(0, "foo")]),
|
||||
"foobar "
|
||||
);
|
||||
assert_eq!(
|
||||
annotate_text(
|
||||
&"foo ".repeat(10),
|
||||
true,
|
||||
&[InlineAnnotation::new(0, "foo ")]
|
||||
),
|
||||
"foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotation_and_overlay() {
|
||||
let annotations = [InlineAnnotation {
|
||||
char_idx: 0,
|
||||
text: "fooo".into(),
|
||||
}];
|
||||
let overlay = [Overlay {
|
||||
char_idx: 0,
|
||||
grapheme: "\t".into(),
|
||||
}];
|
||||
assert_eq!(
|
||||
DocumentFormatter::new_at_prev_checkpoint(
|
||||
"bbar".into(),
|
||||
&TextFormat::new_test(false),
|
||||
TextAnnotations::default()
|
||||
.add_inline_annotations(annotations.as_slice(), None)
|
||||
.add_overlay(overlay.as_slice(), None),
|
||||
0,
|
||||
)
|
||||
.collect_to_str(),
|
||||
"fooo bar "
|
||||
);
|
||||
}
|
@@ -1,334 +0,0 @@
|
||||
//! Support for [EditorConfig](https://EditorConfig.org) configuration loading.
|
||||
//!
|
||||
//! EditorConfig is an editor-agnostic format for specifying configuration in an INI-like, human
|
||||
//! friendly syntax in `.editorconfig` files (which are intended to be checked into VCS). This
|
||||
//! module provides functions to search for all `.editorconfig` files that apply to a given path
|
||||
//! and returns an `EditorConfig` type containing any specified configuration options.
|
||||
//!
|
||||
//! At time of writing, this module follows the [spec](https://spec.editorconfig.org/) at
|
||||
//! version 0.17.2.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
num::{NonZeroU16, NonZeroU8},
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use encoding_rs::Encoding;
|
||||
use globset::{GlobBuilder, GlobMatcher};
|
||||
|
||||
use crate::{
|
||||
indent::{IndentStyle, MAX_INDENT},
|
||||
LineEnding,
|
||||
};
|
||||
|
||||
/// Configuration declared for a path in `.editorconfig` files.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct EditorConfig {
|
||||
pub indent_style: Option<IndentStyle>,
|
||||
pub tab_width: Option<NonZeroU8>,
|
||||
pub line_ending: Option<LineEnding>,
|
||||
pub encoding: Option<&'static Encoding>,
|
||||
// pub spelling_language: Option<SpellingLanguage>,
|
||||
pub trim_trailing_whitespace: Option<bool>,
|
||||
pub insert_final_newline: Option<bool>,
|
||||
pub max_line_length: Option<NonZeroU16>,
|
||||
}
|
||||
|
||||
impl EditorConfig {
|
||||
/// Finds any configuration in `.editorconfig` files which applies to the given path.
|
||||
///
|
||||
/// If no configuration applies then `EditorConfig::default()` is returned.
|
||||
pub fn find(path: &Path) -> Self {
|
||||
let mut configs = Vec::new();
|
||||
// <https://spec.editorconfig.org/#file-processing>
|
||||
for ancestor in path.ancestors() {
|
||||
let editor_config_file = ancestor.join(".editorconfig");
|
||||
let Ok(contents) = fs::read_to_string(&editor_config_file) else {
|
||||
continue;
|
||||
};
|
||||
let ini = match contents.parse::<Ini>() {
|
||||
Ok(ini) => ini,
|
||||
Err(err) => {
|
||||
log::warn!("Ignoring EditorConfig file at '{editor_config_file:?}' because a glob failed to compile: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let is_root = ini.pairs.get("root").map(AsRef::as_ref) == Some("true");
|
||||
configs.push((ini, ancestor));
|
||||
// > The search shall stop if an EditorConfig file is found with the `root` key set to
|
||||
// > `true` in the preamble or when reaching the root filesystem directory.
|
||||
if is_root {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut pairs = Pairs::new();
|
||||
// Reverse the configuration stack so that the `.editorconfig` files closest to `path`
|
||||
// are applied last and overwrite settings in files closer to the search ceiling.
|
||||
//
|
||||
// > If multiple EditorConfig files have matching sections, the pairs from the closer
|
||||
// > EditorConfig file are read last, so pairs in closer files take precedence.
|
||||
for (config, dir) in configs.into_iter().rev() {
|
||||
let relative_path = path.strip_prefix(dir).expect("dir is an ancestor of path");
|
||||
|
||||
for section in config.sections {
|
||||
if section.glob.is_match(relative_path) {
|
||||
log::info!(
|
||||
"applying EditorConfig from section '{}' in file {:?}",
|
||||
section.glob.glob(),
|
||||
dir.join(".editorconfig")
|
||||
);
|
||||
pairs.extend(section.pairs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::from_pairs(pairs)
|
||||
}
|
||||
|
||||
fn from_pairs(pairs: Pairs) -> Self {
|
||||
enum IndentSize {
|
||||
Tab,
|
||||
Spaces(NonZeroU8),
|
||||
}
|
||||
|
||||
// <https://spec.editorconfig.org/#supported-pairs>
|
||||
let indent_size = pairs.get("indent_size").and_then(|value| {
|
||||
if value.as_ref() == "tab" {
|
||||
Some(IndentSize::Tab)
|
||||
} else if let Ok(spaces) = value.parse::<NonZeroU8>() {
|
||||
Some(IndentSize::Spaces(spaces))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let tab_width = pairs
|
||||
.get("tab_width")
|
||||
.and_then(|value| value.parse::<NonZeroU8>().ok())
|
||||
.or(match indent_size {
|
||||
Some(IndentSize::Spaces(spaces)) => Some(spaces),
|
||||
_ => None,
|
||||
});
|
||||
let indent_style = pairs
|
||||
.get("indent_style")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"tab" => Some(IndentStyle::Tabs),
|
||||
"space" => {
|
||||
let spaces = match indent_size {
|
||||
Some(IndentSize::Spaces(spaces)) => spaces.get(),
|
||||
Some(IndentSize::Tab) => tab_width.map(|n| n.get()).unwrap_or(4),
|
||||
None => 4,
|
||||
};
|
||||
Some(IndentStyle::Spaces(spaces.clamp(1, MAX_INDENT)))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let line_ending = pairs
|
||||
.get("end_of_line")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"lf" => Some(LineEnding::LF),
|
||||
"crlf" => Some(LineEnding::Crlf),
|
||||
#[cfg(feature = "unicode-lines")]
|
||||
"cr" => Some(LineEnding::CR),
|
||||
_ => None,
|
||||
});
|
||||
let encoding = pairs.get("charset").and_then(|value| match value.as_ref() {
|
||||
"latin1" => Some(encoding_rs::WINDOWS_1252),
|
||||
"utf-8" => Some(encoding_rs::UTF_8),
|
||||
// `utf-8-bom` is intentionally ignored.
|
||||
// > `utf-8-bom` is discouraged.
|
||||
"utf-16le" => Some(encoding_rs::UTF_16LE),
|
||||
"utf-16be" => Some(encoding_rs::UTF_16BE),
|
||||
_ => None,
|
||||
});
|
||||
let trim_trailing_whitespace =
|
||||
pairs
|
||||
.get("trim_trailing_whitespace")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"true" => Some(true),
|
||||
"false" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
let insert_final_newline = pairs
|
||||
.get("insert_final_newline")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"true" => Some(true),
|
||||
"false" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
// This option is not in the spec but is supported by some editors.
|
||||
// <https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#max_line_length>
|
||||
let max_line_length = pairs
|
||||
.get("max_line_length")
|
||||
.and_then(|value| value.parse::<NonZeroU16>().ok());
|
||||
|
||||
Self {
|
||||
indent_style,
|
||||
tab_width,
|
||||
line_ending,
|
||||
encoding,
|
||||
trim_trailing_whitespace,
|
||||
insert_final_newline,
|
||||
max_line_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Pairs = HashMap<Box<str>, Box<str>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Section {
|
||||
glob: GlobMatcher,
|
||||
pairs: Pairs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Ini {
|
||||
pairs: Pairs,
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl FromStr for Ini {
|
||||
type Err = globset::Error;
|
||||
|
||||
fn from_str(source: &str) -> Result<Self, Self::Err> {
|
||||
// <https://spec.editorconfig.org/#file-format>
|
||||
let mut ini = Ini::default();
|
||||
// > EditorConfig files are in an INI-like file format. To read an EditorConfig file, take
|
||||
// > one line at a time, from beginning to end. For each line:
|
||||
for full_line in source.lines() {
|
||||
// > 1. Remove all leading and trailing whitespace.
|
||||
let line = full_line.trim();
|
||||
// > 2. Process the remaining text as specified for its type below.
|
||||
// > The types of lines are:
|
||||
// > * Blank: contains nothing. Blank lines are ignored.
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// > * Comment: starts with a ';' or '#'. Comment lines are ignored.
|
||||
if line.starts_with([';', '#']) {
|
||||
continue;
|
||||
}
|
||||
if let Some(section) = line.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
|
||||
// > * Section Header: starts with a `[` and ends with a `]`. These lines define
|
||||
// > globs...
|
||||
|
||||
// <https://spec.editorconfig.org/#glob-expressions>
|
||||
// We need to modify the glob string slightly since EditorConfig's glob flavor
|
||||
// doesn't match `globset`'s exactly. `globset` only allows '**' at the beginning
|
||||
// or end of a glob or between two '/'s. (This replacement is not very fancy but
|
||||
// should cover most practical cases.)
|
||||
let mut glob_str = section.replace("**.", "**/*.");
|
||||
if !is_glob_relative(section) {
|
||||
glob_str.insert_str(0, "**/");
|
||||
}
|
||||
let glob = GlobBuilder::new(&glob_str)
|
||||
.literal_separator(true)
|
||||
.backslash_escape(true)
|
||||
.empty_alternates(true)
|
||||
.build()?;
|
||||
ini.sections.push(Section {
|
||||
glob: glob.compile_matcher(),
|
||||
pairs: Pairs::new(),
|
||||
});
|
||||
} else if let Some((key, value)) = line.split_once('=') {
|
||||
// > * Key-Value Pair (or Pair): contains a key and a value, separated by an `=`.
|
||||
// > * Key: The part before the first `=` on the line.
|
||||
// > * Value: The part, if any, after the first `=` on the line.
|
||||
// > * Keys and values are trimmed of leading and trailing whitespace, but
|
||||
// > include any whitespace that is between non-whitespace characters.
|
||||
// > * If a value is not provided, then the value is an empty string.
|
||||
let key = key.trim().to_lowercase().into_boxed_str();
|
||||
let value = value.trim().to_lowercase().into_boxed_str();
|
||||
if let Some(section) = ini.sections.last_mut() {
|
||||
section.pairs.insert(key, value);
|
||||
} else {
|
||||
ini.pairs.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ini)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether a glob is relative to the directory of the config file.
|
||||
fn is_glob_relative(source: &str) -> bool {
|
||||
// > If the glob contains a path separator (a `/` not inside square brackets), then the
|
||||
// > glob is relative to the directory level of the particular `.editorconfig` file itself.
|
||||
let mut idx = 0;
|
||||
while let Some(open) = source[idx..].find('[').map(|open| idx + open) {
|
||||
if source[..open].contains('/') {
|
||||
return true;
|
||||
}
|
||||
idx = source[open..]
|
||||
.find(']')
|
||||
.map_or(source.len(), |close| idx + close);
|
||||
}
|
||||
source[idx..].contains('/')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_glob_relative_test() {
|
||||
assert!(is_glob_relative("subdir/*.c"));
|
||||
assert!(!is_glob_relative("*.txt"));
|
||||
assert!(!is_glob_relative("[a/b].c"));
|
||||
}
|
||||
|
||||
fn editor_config(path: impl AsRef<Path>, source: &str) -> EditorConfig {
|
||||
let path = path.as_ref();
|
||||
let ini = source.parse::<Ini>().unwrap();
|
||||
let pairs = ini
|
||||
.sections
|
||||
.into_iter()
|
||||
.filter(|section| section.glob.is_match(path))
|
||||
.fold(Pairs::new(), |mut acc, section| {
|
||||
acc.extend(section.pairs);
|
||||
acc
|
||||
});
|
||||
EditorConfig::from_pairs(pairs)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_test() {
|
||||
let source = r#"
|
||||
[*]
|
||||
indent_style = space
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[docs/**.txt]
|
||||
insert_final_newline = true
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
editor_config("a.txt", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Spaces(4)),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
editor_config("pkg/Makefile", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Tabs),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
editor_config("docs/config/editor.txt", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Spaces(4)),
|
||||
insert_final_newline: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user