Merge remote-tracking branch 'origin/master'
@ -0,0 +1,281 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
|
||||||
|
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/')}}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
fetch-grammars:
|
||||||
|
name: Fetch Grammars
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- 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@v3
|
||||||
|
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, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
|
||||||
|
include:
|
||||||
|
- build: x86_64-linux
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
cross: false
|
||||||
|
- build: aarch64-linux
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
cross: true
|
||||||
|
- build: riscv64-linux
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: riscv64gc-unknown-linux-gnu
|
||||||
|
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
|
||||||
|
rust: stable
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
cross: false
|
||||||
|
- build: aarch64-macos
|
||||||
|
os: macos-latest
|
||||||
|
rust: stable
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
cross: false
|
||||||
|
skip_tests: true # x86_64 host can't run aarch64 code
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Download grammars
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
|
||||||
|
- name: Move grammars under runtime
|
||||||
|
if: "!startsWith(matrix.os, 'windows')"
|
||||||
|
run: |
|
||||||
|
mkdir -p runtime/grammars/sources
|
||||||
|
tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
|
||||||
|
|
||||||
|
- name: Install ${{ matrix.rust }} toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
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 }}"
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
||||||| f0f295a6
|
||||||
|
- name: Run cargo test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: "!matrix.skip_tests"
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.cross }}
|
||||||
|
command: test
|
||||||
|
args: --release --locked --target ${{ matrix.target }} --workspace
|
||||||
|
|
||||||
|
=======
|
||||||
|
- name: Run cargo test
|
||||||
|
if: "!matrix.skip_tests"
|
||||||
|
run: ${{ env.CARGO }} test --release --locked --target ${{ matrix.target }} --workspace
|
||||||
|
|
||||||
|
>>>>>>> origin/master
|
||||||
|
- name: Set profile.release.strip = true
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cat >> .cargo/config.toml <<EOF
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Build release binary
|
||||||
|
run: ${{ env.CARGO }} build --release --locked --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Build AppImage
|
||||||
|
shell: bash
|
||||||
|
if: matrix.build == 'aarch64-linux' || matrix.build == 'x86_64-linux'
|
||||||
|
run: |
|
||||||
|
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 }}/release/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 archive
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
if [ "${{ matrix.os }}" = "windows-2019" ]; then
|
||||||
|
cp "target/${{ matrix.target }}/release/hx.exe" "dist/"
|
||||||
|
else
|
||||||
|
cp "target/${{ matrix.target }}/release/hx" "dist/"
|
||||||
|
fi
|
||||||
|
if [ -d runtime/grammars/sources ]; then
|
||||||
|
rm -rf runtime/grammars/sources
|
||||||
|
fi
|
||||||
|
cp -r runtime dist
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: bins-${{ matrix.build }}
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish
|
||||||
|
needs: [dist]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
|
||||||
|
- name: Build archive
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
source="$(pwd)"
|
||||||
|
mkdir -p runtime/grammars/sources
|
||||||
|
tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
|
||||||
|
rm -rf grammars
|
||||||
|
|
||||||
|
cd "$(mktemp -d)"
|
||||||
|
mv $source/bins-* .
|
||||||
|
mkdir dist
|
||||||
|
|
||||||
|
for dir in bins-* ; do
|
||||||
|
platform=${dir#"bins-"}
|
||||||
|
if [[ $platform =~ "windows" ]]; then
|
||||||
|
exe=".exe"
|
||||||
|
fi
|
||||||
|
pkgname=helix-$GITHUB_REF_NAME-$platform
|
||||||
|
mkdir $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" = "aarch64-linux" || "$platform" = "x86_64-linux" ]]; then
|
||||||
|
mv bins-$platform/helix-*.AppImage* dist/
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$exe" = "" ]; then
|
||||||
|
tar cJf dist/$pkgname.tar.xz $pkgname
|
||||||
|
else
|
||||||
|
7z a -r dist/$pkgname.zip $pkgname
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
tar cJf dist/helix-$GITHUB_REF_NAME-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 }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
- name: Upload binaries as artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: env.preview == 'true'
|
||||||
|
with:
|
||||||
|
name: release
|
||||||
|
path: dist/*
|
@ -0,0 +1,178 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
|
# Helix Plus
|
||||||
|
|
||||||
|
<h1 style="color:red">This is an unstable fork of helix with some PRs merged and some merge conflicts resolved </h1>
|
||||||
|
|
||||||
|
# Merged PRs
|
||||||
|
|
||||||
|
- [File explorer and tree helper](https://github.com/helix-editor/helix/pull/2377)
|
||||||
|
- [with Icons](https://github.com/r0l1/helix/tree/tree_explorer_icons)
|
||||||
|
- [Add LSP workspace command picker](https://github.com/helix-editor/helix/pull/3140)
|
||||||
|
- [completion fix](https://github.com/helix-editor/helix/pull/1819)
|
||||||
|
- [Add rainbow indentation guides](https://github.com/helix-editor/helix/pull/4056)
|
||||||
|
- [Improve sorting for inline menu (codeaction + completion)](https://github.com/helix-editor/helix/pull/4134)
|
||||||
|
|
||||||
|
And others I forgot about...
|
||||||
|
|
||||||
|
|
||||||
|
# Applied Changes
|
||||||
|
|
||||||
|
- Changed opening the window popup from `ctrl + w` to `ctrl + v`
|
||||||
|
- Added an auto highlight for files in the tree explorer when jumping through opened buffers
|
||||||
|
- Changed some default settings (enabling bufferline, indent guides, the embedded explorer, cursor modes etc.)
|
||||||
|
- Added a `--show-explorer` cli flag to open the file explorer on startup (useful for embedded explorer mode)
|
||||||
|
- Added a `delete` (aliases `rm`, `del`) command to delete the file associated with the current buffer
|
||||||
|
- Changed keybind `<space> E` to close the explorer instead of toggling the recursion one
|
||||||
|
- Added a completion chars setting that triggers autocomplete when typing one of those chars
|
||||||
|
|
||||||
|
- - -
|
||||||
|
||||||| f0f295a6
|
||||||
|
# Helix
|
||||||
|
=======
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
>>>>>>> origin/master
|
||||||
|
|
||||||
|
[![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions)
|
||||||
|
[![GitHub Release](https://img.shields.io/github/v/release/helix-editor/helix)](https://github.com/helix-editor/helix/releases/latest)
|
||||||
|
[![Documentation](https://shields.io/badge/-documentation-452859)](https://docs.helix-editor.com/)
|
||||||
|
[![GitHub contributors](https://img.shields.io/github/contributors/helix-editor/helix)](https://github.com/helix-editor/helix/graphs/contributors)
|
||||||
|
[![Matrix Space](https://img.shields.io/matrix/helix-community:matrix.org)](https://matrix.to/#/#helix-community:matrix.org)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
![Screenshot](./screenshot.png)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For more information, see the [website](https://helix-editor.com) or
|
||||||
|
[documentation](https://docs.helix-editor.com/).
|
||||||
|
|
||||||
|
All shortcuts/keymaps can be found [in the documentation on the website](https://docs.helix-editor.com/keymap.html).
|
||||||
|
|
||||||
|
[Troubleshooting](https://github.com/helix-editor/helix/wiki/Troubleshooting)
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
- Vim-like modal editing
|
||||||
|
- Multiple selections
|
||||||
|
- Built-in language server support
|
||||||
|
- Smart, incremental syntax highlighting and code editing via tree-sitter
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Packages are available for various distributions (see [Installation docs](https://docs.helix-editor.com/install.html)).
|
||||||
|
|
||||||
|
If you would like to build from source:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
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` and build tree-sitter grammars in `./runtime/grammars`.
|
||||||
|
|
||||||
|
Helix 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).
|
||||||
|
|
||||||
|
| OS | Command |
|
||||||
|
| -------------------- | ------------------------------------------------ |
|
||||||
|
| Windows (Cmd) | `xcopy /e /i runtime %AppData%\helix\runtime` |
|
||||||
|
| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
|
||||||
|
| Linux / macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
|
||||||
|
|
||||||
|
Starting with Windows Vista you can also create symbolic links on Windows. Note that this requires
|
||||||
|
elevated privileges - i.e. PowerShell or Cmd must be run as administrator.
|
||||||
|
|
||||||
|
**PowerShell:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
New-Item -ItemType SymbolicLink -Target "runtime" -Path "$Env:AppData\helix\runtime"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cmd:**
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
cd %appdata%\helix
|
||||||
|
mklink /D runtime "<helix-repo>\runtime"
|
||||||
|
```
|
||||||
|
|
||||||
|
The runtime location can be overridden via the `HELIX_RUNTIME` environment variable.
|
||||||
|
|
||||||
|
> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --path helix-term`,
|
||||||
|
> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`.
|
||||||
|
|
||||||
|
If you plan on keeping the repo locally, an alternative to copying/symlinking
|
||||||
|
runtime files is to set `HELIX_RUNTIME=/path/to/helix/runtime`
|
||||||
|
(`HELIX_RUNTIME=$PWD/runtime` if you're in the helix repo directory).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
If you want to customize your `languages.toml` config,
|
||||||
|
tree-sitter grammars may be manually fetched and built with `hx --grammar fetch` and `hx --grammar build`.
|
||||||
|
|
||||||
|
In order to use LSP features like auto-complete, you will need to
|
||||||
|
[install the appropriate Language Server](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
|
||||||
|
for a language.
|
||||||
|
|
||||||
|
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
|
||||||
|
|
||||||
|
## Adding Helix to your desktop environment
|
||||||
|
|
||||||
|
If installing from source, to use Helix in desktop environments that supports [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), including Gnome and KDE, copy the provided `.desktop` file to the correct folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp contrib/Helix.desktop ~/.local/share/applications
|
||||||
|
```
|
||||||
|
|
||||||
|
To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note: there is no icon for Helix yet, so the system default will be used.
|
||||||
|
|
||||||
|
## macOS
|
||||||
|
|
||||||
|
Helix can be installed on macOS through homebrew:
|
||||||
|
|
||||||
|
```
|
||||||
|
brew install helix
|
||||||
|
```
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
# Getting help
|
||||||
|
|
||||||
|
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 [@JakeHL](https://github.com/JakeHL) for designing the logo!
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 15 KiB |
@ -1,6 +1,26 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
const VERSION: &str = include_str!("../VERSION");
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let git_hash = Command::new("git")
|
||||||
|
.args(["rev-parse", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.ok()
|
||||||
|
.filter(|output| output.status.success())
|
||||||
|
.and_then(|x| String::from_utf8(x.stdout).ok());
|
||||||
|
|
||||||
|
let version: Cow<_> = match git_hash {
|
||||||
|
Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
|
||||||
|
None => VERSION.into(),
|
||||||
|
};
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"cargo:rustc-env=BUILD_TARGET={}",
|
"cargo:rustc-env=BUILD_TARGET={}",
|
||||||
std::env::var("TARGET").unwrap()
|
std::env::var("TARGET").unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=../VERSION");
|
||||||
|
println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,9 @@
|
|||||||
use helix_loader::grammar::{build_grammars, fetch_grammars};
|
use helix_loader::grammar::{build_grammars, fetch_grammars};
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
const VERSION: &str = include_str!("../VERSION");
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let git_hash = Command::new("git")
|
|
||||||
.args(["rev-parse", "HEAD"])
|
|
||||||
.output()
|
|
||||||
.ok()
|
|
||||||
.filter(|output| output.status.success())
|
|
||||||
.and_then(|x| String::from_utf8(x.stdout).ok());
|
|
||||||
|
|
||||||
let version: Cow<_> = match git_hash {
|
|
||||||
Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
|
|
||||||
None => VERSION.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() {
|
if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() {
|
||||||
fetch_grammars().expect("Failed to fetch tree-sitter grammars");
|
fetch_grammars().expect("Failed to fetch tree-sitter grammars");
|
||||||
build_grammars(Some(std::env::var("TARGET").unwrap()))
|
build_grammars(Some(std::env::var("TARGET").unwrap()))
|
||||||
.expect("Failed to compile tree-sitter grammars");
|
.expect("Failed to compile tree-sitter grammars");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed=../runtime/grammars/");
|
|
||||||
println!("cargo:rerun-if-changed=../VERSION");
|
|
||||||
|
|
||||||
println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "helix-vcs"
|
||||||
|
version = "0.6.0"
|
||||||
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
|
edition = "2021"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
categories = ["editor"]
|
||||||
|
repository = "https://github.com/helix-editor/helix"
|
||||||
|
homepage = "https://helix-editor.com"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
helix-core = { version = "0.6", path = "../helix-core" }
|
||||||
|
|
||||||
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
|
||||||
|
parking_lot = "0.12"
|
||||||
|
|
||||||
|
git-repository = { version = "0.30", default-features = false , optional = true }
|
||||||
|
imara-diff = "0.1.5"
|
||||||
|
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
git = ["git-repository"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.3"
|
@ -0,0 +1,279 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use helix_core::Rope;
|
||||||
|
use imara_diff::Algorithm;
|
||||||
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||||
|
use tokio::sync::{Notify, OwnedRwLockReadGuard, RwLock};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio::time::Instant;
|
||||||
|
|
||||||
|
use crate::diff::worker::DiffWorker;
|
||||||
|
|
||||||
|
mod line_cache;
|
||||||
|
mod worker;
|
||||||
|
|
||||||
|
type RedrawHandle = (Arc<Notify>, Arc<RwLock<()>>);
|
||||||
|
|
||||||
|
/// A rendering lock passed to the differ the prevents redraws from occurring
|
||||||
|
struct RenderLock {
|
||||||
|
pub lock: OwnedRwLockReadGuard<()>,
|
||||||
|
pub timeout: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
text: Rope,
|
||||||
|
is_base: bool,
|
||||||
|
render_lock: Option<RenderLock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DiffHandle {
|
||||||
|
channel: UnboundedSender<Event>,
|
||||||
|
render_lock: Arc<RwLock<()>>,
|
||||||
|
hunks: Arc<Mutex<Vec<Hunk>>>,
|
||||||
|
inverted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffHandle {
|
||||||
|
pub fn new(diff_base: Rope, doc: Rope, redraw_handle: RedrawHandle) -> DiffHandle {
|
||||||
|
DiffHandle::new_with_handle(diff_base, doc, redraw_handle).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_handle(
|
||||||
|
diff_base: Rope,
|
||||||
|
doc: Rope,
|
||||||
|
redraw_handle: RedrawHandle,
|
||||||
|
) -> (DiffHandle, JoinHandle<()>) {
|
||||||
|
let (sender, receiver) = unbounded_channel();
|
||||||
|
let hunks: Arc<Mutex<Vec<Hunk>>> = Arc::default();
|
||||||
|
let worker = DiffWorker {
|
||||||
|
channel: receiver,
|
||||||
|
hunks: hunks.clone(),
|
||||||
|
new_hunks: Vec::default(),
|
||||||
|
redraw_notify: redraw_handle.0,
|
||||||
|
diff_finished_notify: Arc::default(),
|
||||||
|
};
|
||||||
|
let handle = tokio::spawn(worker.run(diff_base, doc));
|
||||||
|
let differ = DiffHandle {
|
||||||
|
channel: sender,
|
||||||
|
hunks,
|
||||||
|
inverted: false,
|
||||||
|
render_lock: redraw_handle.1,
|
||||||
|
};
|
||||||
|
(differ, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert(&mut self) {
|
||||||
|
self.inverted = !self.inverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hunks(&self) -> FileHunks {
|
||||||
|
FileHunks {
|
||||||
|
hunks: self.hunks.lock(),
|
||||||
|
inverted: self.inverted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the document associated with this redraw handle
|
||||||
|
/// This function is only intended to be called from within the rendering loop
|
||||||
|
/// if called from elsewhere it may fail to acquire the render lock and panic
|
||||||
|
pub fn update_document(&self, doc: Rope, block: bool) -> bool {
|
||||||
|
// unwrap is ok here because the rendering lock is
|
||||||
|
// only exclusively locked during redraw.
|
||||||
|
// This function is only intended to be called
|
||||||
|
// from the core rendering loop where no redraw can happen in parallel
|
||||||
|
let lock = self.render_lock.clone().try_read_owned().unwrap();
|
||||||
|
let timeout = if block {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Instant::now() + tokio::time::Duration::from_millis(SYNC_DIFF_TIMEOUT))
|
||||||
|
};
|
||||||
|
self.update_document_impl(doc, self.inverted, Some(RenderLock { lock, timeout }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_diff_base(&self, diff_base: Rope) -> bool {
|
||||||
|
self.update_document_impl(diff_base, !self.inverted, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_document_impl(
|
||||||
|
&self,
|
||||||
|
text: Rope,
|
||||||
|
is_base: bool,
|
||||||
|
render_lock: Option<RenderLock>,
|
||||||
|
) -> bool {
|
||||||
|
let event = Event {
|
||||||
|
text,
|
||||||
|
is_base,
|
||||||
|
render_lock,
|
||||||
|
};
|
||||||
|
self.channel.send(event).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// synchronous debounce value should be low
|
||||||
|
/// so we can update synchronously most of the time
|
||||||
|
const DIFF_DEBOUNCE_TIME_SYNC: u64 = 1;
|
||||||
|
/// maximum time that rendering should be blocked until the diff finishes
|
||||||
|
const SYNC_DIFF_TIMEOUT: u64 = 12;
|
||||||
|
const DIFF_DEBOUNCE_TIME_ASYNC: u64 = 96;
|
||||||
|
const ALGORITHM: Algorithm = Algorithm::Histogram;
|
||||||
|
const MAX_DIFF_LINES: usize = 64 * u16::MAX as usize;
|
||||||
|
// cap average line length to 128 for files with MAX_DIFF_LINES
|
||||||
|
const MAX_DIFF_BYTES: usize = MAX_DIFF_LINES * 128;
|
||||||
|
|
||||||
|
/// A single change in a file potentially spanning multiple lines
|
||||||
|
/// Hunks produced by the differs are always ordered by their position
|
||||||
|
/// in the file and non-overlapping.
|
||||||
|
/// Specifically for any two hunks `x` and `y` the following properties hold:
|
||||||
|
///
|
||||||
|
/// ``` no_compile
|
||||||
|
/// assert!(x.before.end <= y.before.start);
|
||||||
|
/// assert!(x.after.end <= y.after.start);
|
||||||
|
/// ```
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
|
pub struct Hunk {
|
||||||
|
pub before: Range<u32>,
|
||||||
|
pub after: Range<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hunk {
|
||||||
|
/// Can be used instead of `Option::None` for better performance
|
||||||
|
/// because lines larger then `i32::MAX` are not supported by `imara-diff` anyways.
|
||||||
|
/// Has some nice properties where it usually is not necessary to check for `None` separately:
|
||||||
|
/// Empty ranges fail contains checks and also fails smaller then checks.
|
||||||
|
pub const NONE: Hunk = Hunk {
|
||||||
|
before: u32::MAX..u32::MAX,
|
||||||
|
after: u32::MAX..u32::MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Inverts a change so that `before`
|
||||||
|
pub fn invert(&self) -> Hunk {
|
||||||
|
Hunk {
|
||||||
|
before: self.after.clone(),
|
||||||
|
after: self.before.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_pure_insertion(&self) -> bool {
|
||||||
|
self.before.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_pure_removal(&self) -> bool {
|
||||||
|
self.after.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of changes in a file sorted in ascending
|
||||||
|
/// non-overlapping order
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FileHunks<'a> {
|
||||||
|
hunks: MutexGuard<'a, Vec<Hunk>>,
|
||||||
|
inverted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileHunks<'_> {
|
||||||
|
pub fn is_inverted(&self) -> bool {
|
||||||
|
self.inverted
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `Hunk` for the `n`th change in this file.
|
||||||
|
/// if there is no `n`th change `Hunk::NONE` is returned instead.
|
||||||
|
pub fn nth_hunk(&self, n: u32) -> Hunk {
|
||||||
|
match self.hunks.get(n as usize) {
|
||||||
|
Some(hunk) if self.inverted => hunk.invert(),
|
||||||
|
Some(hunk) => hunk.clone(),
|
||||||
|
None => Hunk::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> u32 {
|
||||||
|
self.hunks.len() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_hunk(&self, line: u32) -> Option<u32> {
|
||||||
|
let hunk_range = if self.inverted {
|
||||||
|
|hunk: &Hunk| hunk.before.clone()
|
||||||
|
} else {
|
||||||
|
|hunk: &Hunk| hunk.after.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = self
|
||||||
|
.hunks
|
||||||
|
.binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
// Search found a hunk that starts exactly at this line, return the next hunk if it exists.
|
||||||
|
Ok(pos) if pos + 1 == self.hunks.len() => None,
|
||||||
|
Ok(pos) => Some(pos as u32 + 1),
|
||||||
|
|
||||||
|
// No hunk starts exactly at this line, so the search returns
|
||||||
|
// the position where a hunk starting at this line should be inserted.
|
||||||
|
// That position is exactly the position of the next hunk or the end
|
||||||
|
// of the list if no such hunk exists
|
||||||
|
Err(pos) if pos == self.hunks.len() => None,
|
||||||
|
Err(pos) => Some(pos as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev_hunk(&self, line: u32) -> Option<u32> {
|
||||||
|
let hunk_range = if self.inverted {
|
||||||
|
|hunk: &Hunk| hunk.before.clone()
|
||||||
|
} else {
|
||||||
|
|hunk: &Hunk| hunk.after.clone()
|
||||||
|
};
|
||||||
|
let res = self
|
||||||
|
.hunks
|
||||||
|
.binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
// Search found a hunk that ends exactly at this line (so it does not include the current line).
|
||||||
|
// We can usually just return that hunk, however a special case for empty hunk is necessary
|
||||||
|
// which represents a pure removal.
|
||||||
|
// Removals are technically empty but are still shown as single line hunks
|
||||||
|
// and as such we must jump to the previous hunk (if it exists) if we are already inside the removal
|
||||||
|
Ok(pos) if !hunk_range(&self.hunks[pos]).is_empty() => Some(pos as u32),
|
||||||
|
|
||||||
|
// No hunk ends exactly at this line, so the search returns
|
||||||
|
// the position where a hunk ending at this line should be inserted.
|
||||||
|
// That position before this one is exactly the position of the previous hunk
|
||||||
|
Err(0) | Ok(0) => None,
|
||||||
|
Err(pos) | Ok(pos) => Some(pos as u32 - 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hunk_at(&self, line: u32, include_removal: bool) -> Option<u32> {
|
||||||
|
let hunk_range = if self.inverted {
|
||||||
|
|hunk: &Hunk| hunk.before.clone()
|
||||||
|
} else {
|
||||||
|
|hunk: &Hunk| hunk.after.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = self
|
||||||
|
.hunks
|
||||||
|
.binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
// Search found a hunk that starts exactly at this line, return it
|
||||||
|
Ok(pos) => Some(pos as u32),
|
||||||
|
|
||||||
|
// No hunk starts exactly at this line, so the search returns
|
||||||
|
// the position where a hunk starting at this line should be inserted.
|
||||||
|
// The previous hunk contains this hunk if it exists and doesn't end before this line
|
||||||
|
Err(0) => None,
|
||||||
|
Err(pos) => {
|
||||||
|
let hunk = hunk_range(&self.hunks[pos - 1]);
|
||||||
|
if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() {
|
||||||
|
Some(pos as u32 - 1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
//! This modules encapsulates a tiny bit of unsafe code that
|
||||||
|
//! makes diffing significantly faster and more ergonomic to implement.
|
||||||
|
//! This code is necessary because diffing requires quick random
|
||||||
|
//! access to the lines of the text that is being diffed.
|
||||||
|
//!
|
||||||
|
//! Therefore it is best to collect the `Rope::lines` iterator into a vec
|
||||||
|
//! first because access to the vec is `O(1)` where `Rope::line` is `O(log N)`.
|
||||||
|
//! However this process can allocate a (potentially quite large) vector.
|
||||||
|
//!
|
||||||
|
//! To avoid reallocation for every diff, the vector is reused.
|
||||||
|
//! However the RopeSlice references the original rope and therefore forms a self-referential data structure.
|
||||||
|
//! A transmute is used to change the lifetime of the slice to static to circumvent that project.
|
||||||
|
use std::mem::transmute;
|
||||||
|
|
||||||
|
use helix_core::{Rope, RopeSlice};
|
||||||
|
use imara_diff::intern::{InternedInput, Interner};
|
||||||
|
|
||||||
|
use super::{MAX_DIFF_BYTES, MAX_DIFF_LINES};
|
||||||
|
|
||||||
|
/// A cache that stores the `lines` of a rope as a vector.
|
||||||
|
/// It allows safely reusing the allocation of the vec when updating the rope
|
||||||
|
pub(crate) struct InternedRopeLines {
|
||||||
|
diff_base: Rope,
|
||||||
|
doc: Rope,
|
||||||
|
num_tokens_diff_base: u32,
|
||||||
|
interned: InternedInput<RopeSlice<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InternedRopeLines {
|
||||||
|
pub fn new(diff_base: Rope, doc: Rope) -> InternedRopeLines {
|
||||||
|
let mut res = InternedRopeLines {
|
||||||
|
interned: InternedInput {
|
||||||
|
before: Vec::with_capacity(diff_base.len_lines()),
|
||||||
|
after: Vec::with_capacity(doc.len_lines()),
|
||||||
|
interner: Interner::new(diff_base.len_lines() + doc.len_lines()),
|
||||||
|
},
|
||||||
|
diff_base,
|
||||||
|
doc,
|
||||||
|
// will be populated by update_diff_base_impl
|
||||||
|
num_tokens_diff_base: 0,
|
||||||
|
};
|
||||||
|
res.update_diff_base_impl();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the `diff_base` and optionally the document if `doc` is not None
|
||||||
|
pub fn update_diff_base(&mut self, diff_base: Rope, doc: Option<Rope>) {
|
||||||
|
self.interned.clear();
|
||||||
|
self.diff_base = diff_base;
|
||||||
|
if let Some(doc) = doc {
|
||||||
|
self.doc = doc
|
||||||
|
}
|
||||||
|
if !self.is_too_large() {
|
||||||
|
self.update_diff_base_impl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the `doc` without reinterning the `diff_base`, this function
|
||||||
|
/// is therefore significantly faster than `update_diff_base` when only the document changes.
|
||||||
|
pub fn update_doc(&mut self, doc: Rope) {
|
||||||
|
// Safety: we clear any tokens that were added after
|
||||||
|
// the interning of `self.diff_base` finished so
|
||||||
|
// all lines that refer to `self.doc` have been purged.
|
||||||
|
|
||||||
|
self.interned
|
||||||
|
.interner
|
||||||
|
.erase_tokens_after(self.num_tokens_diff_base.into());
|
||||||
|
|
||||||
|
self.doc = doc;
|
||||||
|
if self.is_too_large() {
|
||||||
|
self.interned.after.clear();
|
||||||
|
} else {
|
||||||
|
self.update_doc_impl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_diff_base_impl(&mut self) {
|
||||||
|
// Safety: This transmute is safe because it only transmutes a lifetime, which has no effect.
|
||||||
|
// The backing storage for the RopeSlices referred to by the lifetime is stored in `self.diff_base`.
|
||||||
|
// Therefore as long as `self.diff_base` is not dropped/replaced this memory remains valid.
|
||||||
|
// `self.diff_base` is only changed in `self.update_diff_base`, which clears the interner.
|
||||||
|
// When the interned lines are exposed to consumer in `self.diff_input`, the lifetime is bounded to a reference to self.
|
||||||
|
// That means that on calls to update there exist no references to `self.interned`.
|
||||||
|
let before = self
|
||||||
|
.diff_base
|
||||||
|
.lines()
|
||||||
|
.map(|line: RopeSlice| -> RopeSlice<'static> { unsafe { transmute(line) } });
|
||||||
|
self.interned.update_before(before);
|
||||||
|
self.num_tokens_diff_base = self.interned.interner.num_tokens();
|
||||||
|
// the has to be interned again because the interner was fully cleared
|
||||||
|
self.update_doc_impl()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_doc_impl(&mut self) {
|
||||||
|
// Safety: This transmute is save because it only transmutes a lifetime, which has no effect.
|
||||||
|
// The backing storage for the RopeSlices referred to by the lifetime is stored in `self.doc`.
|
||||||
|
// Therefore as long as `self.doc` is not dropped/replaced this memory remains valid.
|
||||||
|
// `self.doc` is only changed in `self.update_doc`, which clears the interner.
|
||||||
|
// When the interned lines are exposed to consumer in `self.diff_input`, the lifetime is bounded to a reference to self.
|
||||||
|
// That means that on calls to update there exist no references to `self.interned`.
|
||||||
|
let after = self
|
||||||
|
.doc
|
||||||
|
.lines()
|
||||||
|
.map(|line: RopeSlice| -> RopeSlice<'static> { unsafe { transmute(line) } });
|
||||||
|
self.interned.update_after(after);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_too_large(&self) -> bool {
|
||||||
|
// bound both lines and bytes to avoid huge files with few (but huge) lines
|
||||||
|
// or huge file with tiny lines. While this makes no difference to
|
||||||
|
// diff itself (the diff performance only depends on the number of tokens)
|
||||||
|
// the interning runtime depends mostly on filesize and is actually dominant
|
||||||
|
// for large files
|
||||||
|
self.doc.len_lines() > MAX_DIFF_LINES
|
||||||
|
|| self.diff_base.len_lines() > MAX_DIFF_LINES
|
||||||
|
|| self.doc.len_bytes() > MAX_DIFF_BYTES
|
||||||
|
|| self.diff_base.len_bytes() > MAX_DIFF_BYTES
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `InternedInput` for performing the diff.
|
||||||
|
/// If `diff_base` or `doc` is so large that performing a diff could slow the editor
|
||||||
|
/// this function returns `None`.
|
||||||
|
pub fn interned_lines(&self) -> Option<&InternedInput<RopeSlice>> {
|
||||||
|
if self.is_too_large() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.interned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
use std::mem::swap;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use helix_core::{Rope, RopeSlice};
|
||||||
|
use imara_diff::intern::InternedInput;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
use tokio::time::{timeout, timeout_at, Duration};
|
||||||
|
|
||||||
|
use crate::diff::{
|
||||||
|
Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::line_cache::InternedRopeLines;
|
||||||
|
use super::Hunk;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
pub(super) struct DiffWorker {
|
||||||
|
pub channel: UnboundedReceiver<Event>,
|
||||||
|
pub hunks: Arc<Mutex<Vec<Hunk>>>,
|
||||||
|
pub new_hunks: Vec<Hunk>,
|
||||||
|
pub redraw_notify: Arc<Notify>,
|
||||||
|
pub diff_finished_notify: Arc<Notify>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffWorker {
|
||||||
|
async fn accumulate_events(&mut self, event: Event) -> (Option<Rope>, Option<Rope>) {
|
||||||
|
let mut accumulator = EventAccumulator::new();
|
||||||
|
accumulator.handle_event(event).await;
|
||||||
|
accumulator
|
||||||
|
.accumulate_debounced_events(
|
||||||
|
&mut self.channel,
|
||||||
|
self.redraw_notify.clone(),
|
||||||
|
self.diff_finished_notify.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
(accumulator.doc, accumulator.diff_base)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(mut self, diff_base: Rope, doc: Rope) {
|
||||||
|
let mut interner = InternedRopeLines::new(diff_base, doc);
|
||||||
|
if let Some(lines) = interner.interned_lines() {
|
||||||
|
self.perform_diff(lines);
|
||||||
|
}
|
||||||
|
self.apply_hunks();
|
||||||
|
while let Some(event) = self.channel.recv().await {
|
||||||
|
let (doc, diff_base) = self.accumulate_events(event).await;
|
||||||
|
|
||||||
|
let process_accumulated_events = || {
|
||||||
|
if let Some(new_base) = diff_base {
|
||||||
|
interner.update_diff_base(new_base, doc)
|
||||||
|
} else {
|
||||||
|
interner.update_doc(doc.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(lines) = interner.interned_lines() {
|
||||||
|
self.perform_diff(lines)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculating diffs is computationally expensive and should
|
||||||
|
// not run inside an async function to avoid blocking other futures.
|
||||||
|
// Note: tokio::task::block_in_place does not work during tests
|
||||||
|
#[cfg(test)]
|
||||||
|
process_accumulated_events();
|
||||||
|
#[cfg(not(test))]
|
||||||
|
tokio::task::block_in_place(process_accumulated_events);
|
||||||
|
|
||||||
|
self.apply_hunks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update the hunks (used by the gutter) by replacing it with `self.new_hunks`.
|
||||||
|
/// `self.new_hunks` is always empty after this function runs.
|
||||||
|
/// To improve performance this function tries to reuse the allocation of the old diff previously stored in `self.line_diffs`
|
||||||
|
fn apply_hunks(&mut self) {
|
||||||
|
swap(&mut *self.hunks.lock(), &mut self.new_hunks);
|
||||||
|
self.diff_finished_notify.notify_waiters();
|
||||||
|
self.new_hunks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_diff(&mut self, input: &InternedInput<RopeSlice>) {
|
||||||
|
imara_diff::diff(ALGORITHM, input, |before: Range<u32>, after: Range<u32>| {
|
||||||
|
self.new_hunks.push(Hunk { before, after })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EventAccumulator {
|
||||||
|
diff_base: Option<Rope>,
|
||||||
|
doc: Option<Rope>,
|
||||||
|
render_lock: Option<RenderLock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EventAccumulator {
|
||||||
|
fn new() -> EventAccumulator {
|
||||||
|
EventAccumulator {
|
||||||
|
diff_base: None,
|
||||||
|
doc: None,
|
||||||
|
render_lock: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_event(&mut self, event: Event) {
|
||||||
|
let dst = if event.is_base {
|
||||||
|
&mut self.diff_base
|
||||||
|
} else {
|
||||||
|
&mut self.doc
|
||||||
|
};
|
||||||
|
|
||||||
|
*dst = Some(event.text);
|
||||||
|
|
||||||
|
// always prefer the most synchronous requested render mode
|
||||||
|
if let Some(render_lock) = event.render_lock {
|
||||||
|
match &mut self.render_lock {
|
||||||
|
Some(RenderLock { timeout, .. }) => {
|
||||||
|
// A timeout of `None` means that the render should
|
||||||
|
// always wait for the diff to complete (so no timeout)
|
||||||
|
// remove the existing timeout, otherwise keep the previous timeout
|
||||||
|
// because it will be shorter then the current timeout
|
||||||
|
if render_lock.timeout.is_none() {
|
||||||
|
timeout.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => self.render_lock = Some(render_lock),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn accumulate_debounced_events(
|
||||||
|
&mut self,
|
||||||
|
channel: &mut UnboundedReceiver<Event>,
|
||||||
|
redraw_notify: Arc<Notify>,
|
||||||
|
diff_finished_notify: Arc<Notify>,
|
||||||
|
) {
|
||||||
|
let async_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_ASYNC);
|
||||||
|
let sync_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_SYNC);
|
||||||
|
loop {
|
||||||
|
// if we are not blocking rendering use a much longer timeout
|
||||||
|
let debounce = if self.render_lock.is_none() {
|
||||||
|
async_debounce
|
||||||
|
} else {
|
||||||
|
sync_debounce
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(Some(event)) = timeout(debounce, channel.recv()).await {
|
||||||
|
self.handle_event(event).await;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup task to trigger the rendering
|
||||||
|
match self.render_lock.take() {
|
||||||
|
// diff is performed outside of the rendering loop
|
||||||
|
// request a redraw after the diff is done
|
||||||
|
None => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
diff_finished_notify.notified().await;
|
||||||
|
redraw_notify.notify_one();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// diff is performed inside the rendering loop
|
||||||
|
// block redraw until the diff is done or the timeout is expired
|
||||||
|
Some(RenderLock {
|
||||||
|
lock,
|
||||||
|
timeout: Some(timeout),
|
||||||
|
}) => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let res = {
|
||||||
|
// Acquire a lock on the redraw handle.
|
||||||
|
// The lock will block the rendering from occurring while held.
|
||||||
|
// The rendering waits for the diff if it doesn't time out
|
||||||
|
timeout_at(timeout, diff_finished_notify.notified()).await
|
||||||
|
};
|
||||||
|
// we either reached the timeout or the diff is finished, release the render lock
|
||||||
|
drop(lock);
|
||||||
|
if res.is_ok() {
|
||||||
|
// Diff finished in time we are done.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Diff failed to complete in time log the event
|
||||||
|
// and wait until the diff occurs to trigger an async redraw
|
||||||
|
log::info!("Diff computation timed out, update of diffs might appear delayed");
|
||||||
|
diff_finished_notify.notified().await;
|
||||||
|
redraw_notify.notify_one();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// a blocking diff is performed inside the rendering loop
|
||||||
|
// block redraw until the diff is done
|
||||||
|
Some(RenderLock {
|
||||||
|
lock,
|
||||||
|
timeout: None,
|
||||||
|
}) => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
diff_finished_notify.notified().await;
|
||||||
|
// diff is done release the lock
|
||||||
|
drop(lock)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
use helix_core::Rope;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
|
use crate::diff::{DiffHandle, Hunk};
|
||||||
|
|
||||||
|
impl DiffHandle {
|
||||||
|
fn new_test(diff_base: &str, doc: &str) -> (DiffHandle, JoinHandle<()>) {
|
||||||
|
DiffHandle::new_with_handle(
|
||||||
|
Rope::from_str(diff_base),
|
||||||
|
Rope::from_str(doc),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
|
||||||
|
let hunks = self.hunks;
|
||||||
|
// dropping the channel terminates the task
|
||||||
|
drop(self.channel);
|
||||||
|
handle.await.unwrap();
|
||||||
|
let hunks = hunks.lock();
|
||||||
|
Vec::clone(&*hunks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn append_line() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test("foo\n", "foo\nbar\n");
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[Hunk {
|
||||||
|
before: 1..1,
|
||||||
|
after: 1..2
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn prepend_line() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test("foo\n", "bar\nfoo\n");
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[Hunk {
|
||||||
|
before: 0..0,
|
||||||
|
after: 0..1
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn modify() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test("foo\nbar\n", "foo bar\nbar\n");
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[Hunk {
|
||||||
|
before: 0..1,
|
||||||
|
after: 0..1
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn delete_line() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test("foo\nfoo bar\nbar\n", "foo\nbar\n");
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[Hunk {
|
||||||
|
before: 1..2,
|
||||||
|
after: 1..1
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn delete_line_and_modify() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test("foo\nbar\ntest\nfoo", "foo\ntest\nfoo bar");
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[
|
||||||
|
Hunk {
|
||||||
|
before: 1..2,
|
||||||
|
after: 1..1
|
||||||
|
},
|
||||||
|
Hunk {
|
||||||
|
before: 3..4,
|
||||||
|
after: 2..3
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn add_use() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test(
|
||||||
|
"use ropey::Rope;\nuse tokio::task::JoinHandle;\n",
|
||||||
|
"use ropey::Rope;\nuse ropey::RopeSlice;\nuse tokio::task::JoinHandle;\n",
|
||||||
|
);
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[Hunk {
|
||||||
|
before: 1..1,
|
||||||
|
after: 1..2
|
||||||
|
},]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_document() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test("foo\nbar\ntest\nfoo", "foo\nbar\ntest\nfoo");
|
||||||
|
differ.update_document(Rope::from_str("foo\ntest\nfoo bar"), false);
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[
|
||||||
|
Hunk {
|
||||||
|
before: 1..2,
|
||||||
|
after: 1..1
|
||||||
|
},
|
||||||
|
Hunk {
|
||||||
|
before: 3..4,
|
||||||
|
after: 2..3
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_base() {
|
||||||
|
let (differ, handle) = DiffHandle::new_test("foo\ntest\nfoo bar", "foo\ntest\nfoo bar");
|
||||||
|
differ.update_diff_base(Rope::from_str("foo\nbar\ntest\nfoo"));
|
||||||
|
let line_diffs = differ.into_diff(handle).await;
|
||||||
|
assert_eq!(
|
||||||
|
&line_diffs,
|
||||||
|
&[
|
||||||
|
Hunk {
|
||||||
|
before: 1..2,
|
||||||
|
after: 1..1
|
||||||
|
},
|
||||||
|
Hunk {
|
||||||
|
before: 3..4,
|
||||||
|
after: 2..3
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use git::objs::tree::EntryMode;
|
||||||
|
use git::sec::trust::DefaultForLevel;
|
||||||
|
use git::{Commit, ObjectId, Repository, ThreadSafeRepository};
|
||||||
|
use git_repository as git;
|
||||||
|
|
||||||
|
use crate::DiffProvider;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
pub struct Git;
|
||||||
|
|
||||||
|
impl Git {
|
||||||
|
fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Option<ThreadSafeRepository> {
|
||||||
|
// custom open options
|
||||||
|
let mut git_open_opts_map = git::sec::trust::Mapping::<git::open::Options>::default();
|
||||||
|
|
||||||
|
// On windows various configuration options are bundled as part of the installations
|
||||||
|
// This path depends on the install location of git and therefore requires some overhead to lookup
|
||||||
|
// This is basically only used on windows and has some overhead hence it's disabled on other platforms.
|
||||||
|
// `gitoxide` doesn't use this as default
|
||||||
|
let config = git::permissions::Config {
|
||||||
|
system: true,
|
||||||
|
git: true,
|
||||||
|
user: true,
|
||||||
|
env: true,
|
||||||
|
includes: true,
|
||||||
|
git_binary: cfg!(windows),
|
||||||
|
};
|
||||||
|
// change options for config permissions without touching anything else
|
||||||
|
git_open_opts_map.reduced = git_open_opts_map.reduced.permissions(git::Permissions {
|
||||||
|
config,
|
||||||
|
..git::Permissions::default_for_level(git::sec::Trust::Reduced)
|
||||||
|
});
|
||||||
|
git_open_opts_map.full = git_open_opts_map.full.permissions(git::Permissions {
|
||||||
|
config,
|
||||||
|
..git::Permissions::default_for_level(git::sec::Trust::Full)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut open_options = git::discover::upwards::Options::default();
|
||||||
|
if let Some(ceiling_dir) = ceiling_dir {
|
||||||
|
open_options.ceiling_dirs = vec![ceiling_dir.to_owned()];
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadSafeRepository::discover_with_environment_overrides_opts(
|
||||||
|
path,
|
||||||
|
open_options,
|
||||||
|
git_open_opts_map,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffProvider for Git {
|
||||||
|
fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>> {
|
||||||
|
debug_assert!(!file.exists() || file.is_file());
|
||||||
|
debug_assert!(file.is_absolute());
|
||||||
|
|
||||||
|
// TODO cache repository lookup
|
||||||
|
let repo = Git::open_repo(file.parent()?, None)?.to_thread_local();
|
||||||
|
let head = repo.head_commit().ok()?;
|
||||||
|
let file_oid = find_file_in_commit(&repo, &head, file)?;
|
||||||
|
|
||||||
|
let file_object = repo.find_object(file_oid).ok()?;
|
||||||
|
let mut data = file_object.detach().data;
|
||||||
|
// convert LF to CRLF if configured to avoid showing every line as changed
|
||||||
|
if repo
|
||||||
|
.config_snapshot()
|
||||||
|
.boolean("core.autocrlf")
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
let mut normalized_file = Vec::with_capacity(data.len());
|
||||||
|
let mut at_cr = false;
|
||||||
|
for &byte in &data {
|
||||||
|
if byte == b'\n' {
|
||||||
|
// if this is a LF instead of a CRLF (last byte was not a CR)
|
||||||
|
// insert a new CR to generate a CRLF
|
||||||
|
if !at_cr {
|
||||||
|
normalized_file.push(b'\r');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
at_cr = byte == b'\r';
|
||||||
|
normalized_file.push(byte)
|
||||||
|
}
|
||||||
|
data = normalized_file
|
||||||
|
}
|
||||||
|
Some(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the object that contains the contents of a file at a specific commit.
|
||||||
|
fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Option<ObjectId> {
|
||||||
|
let repo_dir = repo.work_dir()?;
|
||||||
|
let rel_path = file.strip_prefix(repo_dir).ok()?;
|
||||||
|
let tree = commit.tree().ok()?;
|
||||||
|
let tree_entry = tree.lookup_entry_by_path(rel_path).ok()??;
|
||||||
|
match tree_entry.mode() {
|
||||||
|
// not a file, everything is new, do not show diff
|
||||||
|
EntryMode::Tree | EntryMode::Commit | EntryMode::Link => None,
|
||||||
|
// found a file
|
||||||
|
EntryMode::Blob | EntryMode::BlobExecutable => Some(tree_entry.object_id()),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
use std::{fs::File, io::Write, path::Path, process::Command};
|
||||||
|
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
use crate::{DiffProvider, Git};
|
||||||
|
|
||||||
|
fn exec_git_cmd(args: &str, git_dir: &Path) {
|
||||||
|
let res = Command::new("git")
|
||||||
|
.arg("-C")
|
||||||
|
.arg(git_dir) // execute the git command in this directory
|
||||||
|
.args(args.split_whitespace())
|
||||||
|
.env_remove("GIT_DIR")
|
||||||
|
.env_remove("GIT_ASKPASS")
|
||||||
|
.env_remove("SSH_ASKPASS")
|
||||||
|
.env("GIT_TERMINAL_PROMPT", "false")
|
||||||
|
.env("GIT_AUTHOR_DATE", "2000-01-01 00:00:00 +0000")
|
||||||
|
.env("GIT_AUTHOR_EMAIL", "author@example.com")
|
||||||
|
.env("GIT_AUTHOR_NAME", "author")
|
||||||
|
.env("GIT_COMMITTER_DATE", "2000-01-02 00:00:00 +0000")
|
||||||
|
.env("GIT_COMMITTER_EMAIL", "committer@example.com")
|
||||||
|
.env("GIT_COMMITTER_NAME", "committer")
|
||||||
|
.env("GIT_CONFIG_COUNT", "2")
|
||||||
|
.env("GIT_CONFIG_KEY_0", "commit.gpgsign")
|
||||||
|
.env("GIT_CONFIG_VALUE_0", "false")
|
||||||
|
.env("GIT_CONFIG_KEY_1", "init.defaultBranch")
|
||||||
|
.env("GIT_CONFIG_VALUE_1", "main")
|
||||||
|
.output()
|
||||||
|
.unwrap_or_else(|_| panic!("`git {args}` failed"));
|
||||||
|
if !res.status.success() {
|
||||||
|
println!("{}", String::from_utf8_lossy(&res.stdout));
|
||||||
|
eprintln!("{}", String::from_utf8_lossy(&res.stderr));
|
||||||
|
panic!("`git {args}` failed (see output above)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_commit(repo: &Path, add_modified: bool) {
|
||||||
|
if add_modified {
|
||||||
|
exec_git_cmd("add -A", repo);
|
||||||
|
}
|
||||||
|
exec_git_cmd("commit -m message", repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_git_repo() -> TempDir {
|
||||||
|
let tmp = tempfile::tempdir().expect("create temp dir for git testing");
|
||||||
|
exec_git_cmd("init", tmp.path());
|
||||||
|
exec_git_cmd("config user.email test@helix.org", tmp.path());
|
||||||
|
exec_git_cmd("config user.name helix-test", tmp.path());
|
||||||
|
tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_file() {
|
||||||
|
let temp_git = empty_git_repo();
|
||||||
|
let file = temp_git.path().join("file.txt");
|
||||||
|
File::create(&file).unwrap().write_all(b"foo").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(Git.get_diff_base(&file), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unmodified_file() {
|
||||||
|
let temp_git = empty_git_repo();
|
||||||
|
let file = temp_git.path().join("file.txt");
|
||||||
|
let contents = b"foo".as_slice();
|
||||||
|
File::create(&file).unwrap().write_all(contents).unwrap();
|
||||||
|
create_commit(temp_git.path(), true);
|
||||||
|
assert_eq!(Git.get_diff_base(&file), Some(Vec::from(contents)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modified_file() {
|
||||||
|
let temp_git = empty_git_repo();
|
||||||
|
let file = temp_git.path().join("file.txt");
|
||||||
|
let contents = b"foo".as_slice();
|
||||||
|
File::create(&file).unwrap().write_all(contents).unwrap();
|
||||||
|
create_commit(temp_git.path(), true);
|
||||||
|
File::create(&file).unwrap().write_all(b"bar").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(Git.get_diff_base(&file), Some(Vec::from(contents)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that `get_file_head` does not return content for a directory.
|
||||||
|
/// This is important to correctly cover cases where a directory is removed and replaced by a file.
|
||||||
|
/// If the contents of the directory object were returned a diff between a path and the directory children would be produced.
|
||||||
|
#[test]
|
||||||
|
fn directory() {
|
||||||
|
let temp_git = empty_git_repo();
|
||||||
|
let dir = temp_git.path().join("file.txt");
|
||||||
|
std::fs::create_dir(&dir).expect("");
|
||||||
|
let file = dir.join("file.txt");
|
||||||
|
let contents = b"foo".as_slice();
|
||||||
|
File::create(&file).unwrap().write_all(contents).unwrap();
|
||||||
|
|
||||||
|
create_commit(temp_git.path(), true);
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(&dir).unwrap();
|
||||||
|
File::create(&dir).unwrap().write_all(b"bar").unwrap();
|
||||||
|
assert_eq!(Git.get_diff_base(&dir), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that `get_file_head` does not return content for a symlink.
|
||||||
|
/// This is important to correctly cover cases where a symlink is removed and replaced by a file.
|
||||||
|
/// If the contents of the symlink object were returned a diff between a path and the actual file would be produced (bad ui).
|
||||||
|
#[cfg(any(unix, windows))]
|
||||||
|
#[test]
|
||||||
|
fn symlink() {
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::symlink;
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
use std::os::windows::fs::symlink_file as symlink;
|
||||||
|
let temp_git = empty_git_repo();
|
||||||
|
let file = temp_git.path().join("file.txt");
|
||||||
|
let contents = b"foo".as_slice();
|
||||||
|
File::create(&file).unwrap().write_all(contents).unwrap();
|
||||||
|
let file_link = temp_git.path().join("file_link.txt");
|
||||||
|
symlink("file.txt", &file_link).unwrap();
|
||||||
|
|
||||||
|
create_commit(temp_git.path(), true);
|
||||||
|
assert_eq!(Git.get_diff_base(&file_link), None);
|
||||||
|
assert_eq!(Git.get_diff_base(&file), Some(Vec::from(contents)));
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[cfg(feature = "git")]
|
||||||
|
pub use git::Git;
|
||||||
|
#[cfg(not(feature = "git"))]
|
||||||
|
pub use Dummy as Git;
|
||||||
|
|
||||||
|
#[cfg(feature = "git")]
|
||||||
|
mod git;
|
||||||
|
|
||||||
|
mod diff;
|
||||||
|
|
||||||
|
pub use diff::{DiffHandle, Hunk};
|
||||||
|
|
||||||
|
pub trait DiffProvider {
|
||||||
|
/// Returns the data that a diff should be computed against
|
||||||
|
/// if this provider is used.
|
||||||
|
/// The data is returned as raw byte without any decoding or encoding performed
|
||||||
|
/// to ensure all file encodings are handled correctly.
|
||||||
|
fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Dummy;
|
||||||
|
impl DiffProvider for Dummy {
|
||||||
|
fn get_diff_base(&self, _file: &Path) -> Option<Vec<u8>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DiffProviderRegistry {
|
||||||
|
providers: Vec<Box<dyn DiffProvider>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffProviderRegistry {
|
||||||
|
pub fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>> {
|
||||||
|
self.providers
|
||||||
|
.iter()
|
||||||
|
.find_map(|provider| provider.get_diff_base(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiffProviderRegistry {
|
||||||
|
fn default() -> Self {
|
||||||
|
// currently only git is supported
|
||||||
|
// TODO make this configurable when more providers are added
|
||||||
|
let git: Box<dyn DiffProvider> = Box::new(Git);
|
||||||
|
let providers = vec![git];
|
||||||
|
DiffProviderRegistry { providers }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" viewBox="663.38 37.57 575.35 903.75"> <g transform="matrix(1,0,0,1,-31352.7,-1817.25)"> <g transform="matrix(1,0,0,1,31062.7,-20.8972)"> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1083.58,1875.72L1635.06,2194.12C1649.8,2202.63 1658.88,2218.37 1658.88,2235.39C1658.88,2264.98 1658.88,2311.74 1658.88,2341.33C1658.88,2349.84 1656.61,2358.03 1652.5,2365.16C1652.5,2365.16 1214.7,2112.4 1107.2,2050.33C1092.58,2041.89 1083.58,2026.29 1083.58,2009.41C1083.58,1963.5 1083.58,1875.72 1083.58,1875.72Z" style="fill:#706bc8;"></path> </g> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1635.26,2604.84C1649.88,2613.28 1658.88,2628.87 1658.88,2645.75C1658.88,2691.67 1658.88,2779.44 1658.88,2779.44L1107.41,2461.05C1092.66,2452.53 1083.58,2436.8 1083.58,2419.78C1083.58,2390.19 1083.58,2343.42 1083.58,2313.84C1083.58,2305.32 1085.85,2297.13 1089.96,2290.01C1089.96,2290.01 1527.76,2542.77 1635.26,2604.84Z" style="fill:#55c5e4;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1432.56C785.214,1435.55 780.717,1439.9 777.509,1445.46C767.862,1462.16 773.473,1483.76 790.004,1493.59L789.998,1493.59L761.173,1476.95C746.427,1468.44 737.344,1452.71 737.344,1435.68C737.344,1406.09 737.344,1359.33 737.344,1329.74C737.344,1312.71 746.427,1296.98 761.173,1288.47L1259.59,1000.74L1259.83,1000.6C1264.92,997.617 1269.33,993.314 1272.48,987.844C1282.13,971.136 1276.52,949.544 1259.99,939.707L1260,939.707L1288.82,956.349C1303.57,964.862 1312.65,980.595 1312.65,997.622C1312.65,1027.21 1312.65,1073.97 1312.65,1103.56C1312.65,1120.59 1303.57,1136.32 1288.82,1144.83L1259.19,1161.94L1259.59,1161.68L790.407,1432.56Z" style="fill:#84ddea;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1686.24C785.214,1689.23 780.717,1693.58 777.509,1699.13C767.862,1715.84 773.473,1737.43 790.004,1747.27L789.998,1747.27L761.173,1730.63C746.427,1722.12 737.344,1706.38 737.344,1689.36C737.344,1659.77 737.344,1613.01 737.344,1583.42C737.344,1566.39 746.427,1550.66 761.173,1542.15L1259.59,1254.42L1259.83,1254.28C1264.92,1251.29 1269.33,1246.99 1272.48,1241.52C1282.13,1224.81 1276.52,1203.22 1259.99,1193.38L1260,1193.38L1288.82,1210.03C1303.57,1218.54 1312.65,1234.27 1312.65,1251.3C1312.65,1280.89 1312.65,1327.65 1312.65,1357.24C1312.65,1374.26 1303.57,1390 1288.82,1398.51L1259.19,1415.61L1259.59,1415.36L790.407,1686.24Z" style="fill:#997bc8;"></path></g></g></g> </svg>
|
After Width: | Height: | Size: 2.8 KiB |
@ -0,0 +1,115 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
viewBox="663.38 37.57 2087.006 903.71997"
|
||||||
|
id="svg22"
|
||||||
|
sodipodi:docname="logo_dark.svg"
|
||||||
|
width="2087.0059"
|
||||||
|
height="903.71997"
|
||||||
|
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs26"><rect
|
||||||
|
x="713.02588"
|
||||||
|
y="-304.32538"
|
||||||
|
width="3615.2336"
|
||||||
|
height="1864.7544"
|
||||||
|
id="rect14663" /><rect
|
||||||
|
x="972.073"
|
||||||
|
y="151.15895"
|
||||||
|
width="2140.9646"
|
||||||
|
height="684.86273"
|
||||||
|
id="rect447" /><rect
|
||||||
|
x="897.0401"
|
||||||
|
y="217.45384"
|
||||||
|
width="837.72321"
|
||||||
|
height="631.59924"
|
||||||
|
id="rect435" /><rect
|
||||||
|
x="825.67834"
|
||||||
|
y="157.61452"
|
||||||
|
width="1496.2448"
|
||||||
|
height="861.45544"
|
||||||
|
id="rect429" /><rect
|
||||||
|
x="798.3819"
|
||||||
|
y="-42.157242"
|
||||||
|
width="2236.0837"
|
||||||
|
height="945.90723"
|
||||||
|
id="rect315" /><rect
|
||||||
|
x="661.30237"
|
||||||
|
y="48.087799"
|
||||||
|
width="769.15619"
|
||||||
|
height="828.46844"
|
||||||
|
id="rect309" /></defs><sodipodi:namedview
|
||||||
|
id="namedview24"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.28409405"
|
||||||
|
inkscape:cx="1904.2989"
|
||||||
|
inkscape:cy="633.59299"
|
||||||
|
inkscape:window-width="1908"
|
||||||
|
inkscape:window-height="2075"
|
||||||
|
inkscape:window-x="26"
|
||||||
|
inkscape:window-y="23"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg22" /> <g
|
||||||
|
transform="translate(-31352.726,-1817.2547)"
|
||||||
|
id="g20"> <g
|
||||||
|
transform="translate(31062.7,-20.8972)"
|
||||||
|
id="g18"> <g
|
||||||
|
transform="translate(-130.173,0.00185558)"
|
||||||
|
id="g4"> <path
|
||||||
|
d="m 1083.58,1875.72 551.48,318.4 c 14.74,8.51 23.82,24.25 23.82,41.27 0,29.59 0,76.35 0,105.94 0,8.51 -2.27,16.7 -6.38,23.83 0,0 -437.8,-252.76 -545.3,-314.83 -14.62,-8.44 -23.62,-24.04 -23.62,-40.92 0,-45.91 0,-133.69 0,-133.69 z"
|
||||||
|
style="fill:#706bc8"
|
||||||
|
id="path2" /> </g> <g
|
||||||
|
transform="translate(-130.173,0.00185558)"
|
||||||
|
id="g8"> <path
|
||||||
|
d="m 1635.26,2604.84 c 14.62,8.44 23.62,24.03 23.62,40.91 0,45.92 0,133.69 0,133.69 l -551.47,-318.39 c -14.75,-8.52 -23.83,-24.25 -23.83,-41.27 0,-29.59 0,-76.36 0,-105.94 0,-8.52 2.27,-16.71 6.38,-23.83 0,0 437.8,252.76 545.3,314.83 z"
|
||||||
|
style="fill:#55c5e4"
|
||||||
|
id="path6" /> </g> <g
|
||||||
|
transform="translate(216.062,984.098)"
|
||||||
|
id="g12"> <path
|
||||||
|
d="m 790.407,1432.56 c -5.193,2.99 -9.69,7.34 -12.898,12.9 -9.647,16.7 -4.036,38.3 12.495,48.13 h -0.006 l -28.825,-16.64 c -14.746,-8.51 -23.829,-24.24 -23.829,-41.27 0,-29.59 0,-76.35 0,-105.94 0,-17.03 9.083,-32.76 23.829,-41.27 l 498.417,-287.73 0.24,-0.14 c 5.09,-2.983 9.5,-7.286 12.65,-12.756 9.65,-16.708 4.04,-38.3 -12.49,-48.137 h 0.01 l 28.82,16.642 c 14.75,8.513 23.83,24.246 23.83,41.273 0,29.588 0,76.348 0,105.938 0,17.03 -9.08,32.76 -23.83,41.27 l -29.63,17.11 0.4,-0.26 z"
|
||||||
|
style="fill:#84ddea"
|
||||||
|
id="path10" /> </g> <g
|
||||||
|
transform="translate(216.062,984.098)"
|
||||||
|
id="g16"> <path
|
||||||
|
d="m 790.407,1686.24 c -5.193,2.99 -9.69,7.34 -12.898,12.89 -9.647,16.71 -4.036,38.3 12.495,48.14 h -0.006 l -28.825,-16.64 c -14.746,-8.51 -23.829,-24.25 -23.829,-41.27 0,-29.59 0,-76.35 0,-105.94 0,-17.03 9.083,-32.76 23.829,-41.27 l 498.417,-287.73 0.24,-0.14 c 5.09,-2.99 9.5,-7.29 12.65,-12.76 9.65,-16.71 4.04,-38.3 -12.49,-48.14 h 0.01 l 28.82,16.65 c 14.75,8.51 23.83,24.24 23.83,41.27 0,29.59 0,76.35 0,105.94 0,17.02 -9.08,32.76 -23.83,41.27 l -29.63,17.1 0.4,-0.25 z"
|
||||||
|
style="fill:#997bc8"
|
||||||
|
id="path14" /></g></g></g> <text
|
||||||
|
xml:space="preserve"
|
||||||
|
transform="translate(663.354,37.565425)"
|
||||||
|
id="text307"
|
||||||
|
style="font-size:4px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';white-space:pre;shape-inside:url(#rect309);display:inline;fill:#006400;stroke:#006400;stroke-width:2.66667" /><g
|
||||||
|
aria-label="Helix"
|
||||||
|
transform="matrix(1.3113898,0,0,1.3113898,142.0244,48.21073)"
|
||||||
|
id="text445"
|
||||||
|
style="font-size:4px;-inkscape-font-specification:'sans-serif, Normal';white-space:pre;shape-inside:url(#rect447);display:inline;fill:#f0f6fc;stroke:#f0f6fc;stroke-width:2.66687;stroke-opacity:1;fill-opacity:1"><path
|
||||||
|
d="m 1242.0723,515.10828 h -60.4 v -123.2 h -113.2 v 123.2 h -60.4 v -285.6 h 60.4 v 112 h 113.2 v -112 h 60.4 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700;stroke:#f0f6fc;stroke-opacity:1;fill:#f0f6fc;fill-opacity:1"
|
||||||
|
id="path14794" /><path
|
||||||
|
d="m 1399.272,292.70828 q 30.4,0 52,11.6 22,11.6 34,33.6 12,22 12,54 v 28.8 h -140.8 q 0.8,25.2 14.8,39.6 14.4,14.4 39.6,14.4 21.2,0 38.4,-4 17.2,-4.4 35.6,-13.2 v 46 q -16,8 -34,11.6 -17.6,4 -42.8,4 -32.8,0 -58,-12 -25.2,-12.4 -39.6,-37.2 -14.4,-24.8 -14.4,-62.4 0,-38.4 12.8,-63.6 13.2,-25.6 36.4,-38.4 23.2,-12.8 54,-12.8 z m 0.4,42.4 q -17.2,0 -28.8,11.2 -11.2,11.2 -13.2,34.8 h 83.6 q 0,-13.2 -4.8,-23.6 -4.4,-10.4 -13.6,-16.4 -9.2,-6 -23.2,-6 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700;stroke:#f0f6fc;stroke-opacity:1;fill:#f0f6fc;fill-opacity:1"
|
||||||
|
id="path14796" /><path
|
||||||
|
d="m 1605.2719,515.10828 h -59.6 v -304 h 59.6 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700;stroke:#f0f6fc;stroke-opacity:1;fill:#f0f6fc;fill-opacity:1"
|
||||||
|
id="path14798" /><path
|
||||||
|
d="m 1727.272,296.70828 v 218.4 h -59.6 v -218.4 z m -29.6,-85.6 q 13.2,0 22.8,6.4 9.6,6 9.6,22.8 0,16.4 -9.6,22.8 -9.6,6.4 -22.8,6.4 -13.6,0 -23.2,-6.4 -9.2,-6.4 -9.2,-22.8 0,-16.8 9.2,-22.8 9.6,-6.4 23.2,-6.4 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700;stroke:#f0f6fc;stroke-opacity:1;fill:#f0f6fc;fill-opacity:1"
|
||||||
|
id="path14800" /><path
|
||||||
|
d="m 1834.4721,403.50828 -70.4,-106.8 h 67.6 l 42.4,69.6 42.8,-69.6 h 67.6 l -71.2,106.8 74.4,111.6 h -67.6 l -46,-74.8 -46,74.8 h -67.6 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700;stroke:#f0f6fc;stroke-opacity:1;fill:#f0f6fc;fill-opacity:1"
|
||||||
|
id="path14802" /></g><text
|
||||||
|
xml:space="preserve"
|
||||||
|
transform="translate(663.38,37.570044)"
|
||||||
|
id="text14661"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:997.723px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, @wght=700';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-variation-settings:'wght' 700;white-space:pre;shape-inside:url(#rect14663);display:inline;fill:#2a292f;fill-opacity:1;stroke:#2a292f;stroke-width:6.652;stroke-dasharray:none;stroke-opacity:1" /></svg>
|
After Width: | Height: | Size: 7.1 KiB |
@ -0,0 +1,115 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
viewBox="663.38 37.57 2087.006 903.71997"
|
||||||
|
id="svg22"
|
||||||
|
sodipodi:docname="logo_light.svg"
|
||||||
|
width="2087.0059"
|
||||||
|
height="903.71997"
|
||||||
|
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs26"><rect
|
||||||
|
x="713.02588"
|
||||||
|
y="-304.32538"
|
||||||
|
width="3615.2336"
|
||||||
|
height="1864.7544"
|
||||||
|
id="rect14663" /><rect
|
||||||
|
x="972.073"
|
||||||
|
y="151.15895"
|
||||||
|
width="2140.9646"
|
||||||
|
height="684.86273"
|
||||||
|
id="rect447" /><rect
|
||||||
|
x="897.0401"
|
||||||
|
y="217.45384"
|
||||||
|
width="837.72321"
|
||||||
|
height="631.59924"
|
||||||
|
id="rect435" /><rect
|
||||||
|
x="825.67834"
|
||||||
|
y="157.61452"
|
||||||
|
width="1496.2448"
|
||||||
|
height="861.45544"
|
||||||
|
id="rect429" /><rect
|
||||||
|
x="798.3819"
|
||||||
|
y="-42.157242"
|
||||||
|
width="2236.0837"
|
||||||
|
height="945.90723"
|
||||||
|
id="rect315" /><rect
|
||||||
|
x="661.30237"
|
||||||
|
y="48.087799"
|
||||||
|
width="769.15619"
|
||||||
|
height="828.46844"
|
||||||
|
id="rect309" /></defs><sodipodi:namedview
|
||||||
|
id="namedview24"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.28409405"
|
||||||
|
inkscape:cx="1904.2989"
|
||||||
|
inkscape:cy="633.59299"
|
||||||
|
inkscape:window-width="1908"
|
||||||
|
inkscape:window-height="2075"
|
||||||
|
inkscape:window-x="26"
|
||||||
|
inkscape:window-y="23"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg22" /> <g
|
||||||
|
transform="translate(-31352.726,-1817.2547)"
|
||||||
|
id="g20"> <g
|
||||||
|
transform="translate(31062.7,-20.8972)"
|
||||||
|
id="g18"> <g
|
||||||
|
transform="translate(-130.173,0.00185558)"
|
||||||
|
id="g4"> <path
|
||||||
|
d="m 1083.58,1875.72 551.48,318.4 c 14.74,8.51 23.82,24.25 23.82,41.27 0,29.59 0,76.35 0,105.94 0,8.51 -2.27,16.7 -6.38,23.83 0,0 -437.8,-252.76 -545.3,-314.83 -14.62,-8.44 -23.62,-24.04 -23.62,-40.92 0,-45.91 0,-133.69 0,-133.69 z"
|
||||||
|
style="fill:#706bc8"
|
||||||
|
id="path2" /> </g> <g
|
||||||
|
transform="translate(-130.173,0.00185558)"
|
||||||
|
id="g8"> <path
|
||||||
|
d="m 1635.26,2604.84 c 14.62,8.44 23.62,24.03 23.62,40.91 0,45.92 0,133.69 0,133.69 l -551.47,-318.39 c -14.75,-8.52 -23.83,-24.25 -23.83,-41.27 0,-29.59 0,-76.36 0,-105.94 0,-8.52 2.27,-16.71 6.38,-23.83 0,0 437.8,252.76 545.3,314.83 z"
|
||||||
|
style="fill:#55c5e4"
|
||||||
|
id="path6" /> </g> <g
|
||||||
|
transform="translate(216.062,984.098)"
|
||||||
|
id="g12"> <path
|
||||||
|
d="m 790.407,1432.56 c -5.193,2.99 -9.69,7.34 -12.898,12.9 -9.647,16.7 -4.036,38.3 12.495,48.13 h -0.006 l -28.825,-16.64 c -14.746,-8.51 -23.829,-24.24 -23.829,-41.27 0,-29.59 0,-76.35 0,-105.94 0,-17.03 9.083,-32.76 23.829,-41.27 l 498.417,-287.73 0.24,-0.14 c 5.09,-2.983 9.5,-7.286 12.65,-12.756 9.65,-16.708 4.04,-38.3 -12.49,-48.137 h 0.01 l 28.82,16.642 c 14.75,8.513 23.83,24.246 23.83,41.273 0,29.588 0,76.348 0,105.938 0,17.03 -9.08,32.76 -23.83,41.27 l -29.63,17.11 0.4,-0.26 z"
|
||||||
|
style="fill:#84ddea"
|
||||||
|
id="path10" /> </g> <g
|
||||||
|
transform="translate(216.062,984.098)"
|
||||||
|
id="g16"> <path
|
||||||
|
d="m 790.407,1686.24 c -5.193,2.99 -9.69,7.34 -12.898,12.89 -9.647,16.71 -4.036,38.3 12.495,48.14 h -0.006 l -28.825,-16.64 c -14.746,-8.51 -23.829,-24.25 -23.829,-41.27 0,-29.59 0,-76.35 0,-105.94 0,-17.03 9.083,-32.76 23.829,-41.27 l 498.417,-287.73 0.24,-0.14 c 5.09,-2.99 9.5,-7.29 12.65,-12.76 9.65,-16.71 4.04,-38.3 -12.49,-48.14 h 0.01 l 28.82,16.65 c 14.75,8.51 23.83,24.24 23.83,41.27 0,29.59 0,76.35 0,105.94 0,17.02 -9.08,32.76 -23.83,41.27 l -29.63,17.1 0.4,-0.25 z"
|
||||||
|
style="fill:#997bc8"
|
||||||
|
id="path14" /></g></g></g> <text
|
||||||
|
xml:space="preserve"
|
||||||
|
transform="translate(663.354,37.565425)"
|
||||||
|
id="text307"
|
||||||
|
style="font-size:4px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';white-space:pre;shape-inside:url(#rect309);display:inline;fill:#006400;stroke:#006400;stroke-width:2.66667" /><g
|
||||||
|
aria-label="Helix"
|
||||||
|
transform="matrix(1.3113898,0,0,1.3113898,142.0244,48.21073)"
|
||||||
|
id="text445"
|
||||||
|
style="font-size:4px;-inkscape-font-specification:'sans-serif, Normal';white-space:pre;shape-inside:url(#rect447);display:inline;fill:#2a292f;stroke:#2a292f;stroke-width:2.66687"><path
|
||||||
|
d="m 1242.0723,515.10828 h -60.4 v -123.2 h -113.2 v 123.2 h -60.4 v -285.6 h 60.4 v 112 h 113.2 v -112 h 60.4 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700"
|
||||||
|
id="path14794" /><path
|
||||||
|
d="m 1399.272,292.70828 q 30.4,0 52,11.6 22,11.6 34,33.6 12,22 12,54 v 28.8 h -140.8 q 0.8,25.2 14.8,39.6 14.4,14.4 39.6,14.4 21.2,0 38.4,-4 17.2,-4.4 35.6,-13.2 v 46 q -16,8 -34,11.6 -17.6,4 -42.8,4 -32.8,0 -58,-12 -25.2,-12.4 -39.6,-37.2 -14.4,-24.8 -14.4,-62.4 0,-38.4 12.8,-63.6 13.2,-25.6 36.4,-38.4 23.2,-12.8 54,-12.8 z m 0.4,42.4 q -17.2,0 -28.8,11.2 -11.2,11.2 -13.2,34.8 h 83.6 q 0,-13.2 -4.8,-23.6 -4.4,-10.4 -13.6,-16.4 -9.2,-6 -23.2,-6 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700"
|
||||||
|
id="path14796" /><path
|
||||||
|
d="m 1605.2719,515.10828 h -59.6 v -304 h 59.6 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700"
|
||||||
|
id="path14798" /><path
|
||||||
|
d="m 1727.272,296.70828 v 218.4 h -59.6 v -218.4 z m -29.6,-85.6 q 13.2,0 22.8,6.4 9.6,6 9.6,22.8 0,16.4 -9.6,22.8 -9.6,6.4 -22.8,6.4 -13.6,0 -23.2,-6.4 -9.2,-6.4 -9.2,-22.8 0,-16.8 9.2,-22.8 9.6,-6.4 23.2,-6.4 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700"
|
||||||
|
id="path14800" /><path
|
||||||
|
d="m 1834.4721,403.50828 -70.4,-106.8 h 67.6 l 42.4,69.6 42.8,-69.6 h 67.6 l -71.2,106.8 74.4,111.6 h -67.6 l -46,-74.8 -46,74.8 h -67.6 z"
|
||||||
|
style="font-size:400px;-inkscape-font-specification:'sans-serif, @wght=700';font-variation-settings:'wght' 700"
|
||||||
|
id="path14802" /></g><text
|
||||||
|
xml:space="preserve"
|
||||||
|
transform="translate(663.38,37.570044)"
|
||||||
|
id="text14661"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:997.723px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, @wght=700';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-variation-settings:'wght' 700;white-space:pre;shape-inside:url(#rect14663);display:inline;fill:#2a292f;fill-opacity:1;stroke:#2a292f;stroke-width:6.652;stroke-dasharray:none;stroke-opacity:1" /></svg>
|
After Width: | Height: | Size: 6.8 KiB |
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
(function_definition)
|
||||||
|
(if_statement)
|
||||||
|
(for_statement)
|
||||||
|
(case_statement)
|
||||||
|
(pipeline)
|
||||||
|
] @indent
|
||||||
|
|
||||||
|
[
|
||||||
|
"}"
|
||||||
|
] @outdent
|
@ -0,0 +1,47 @@
|
|||||||
|
[
|
||||||
|
(string_type)
|
||||||
|
(preamble_type)
|
||||||
|
(entry_type)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
(junk)
|
||||||
|
(comment)
|
||||||
|
] @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
"="
|
||||||
|
"#"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
(command) @function.builtin
|
||||||
|
|
||||||
|
(number) @constant.numeric
|
||||||
|
|
||||||
|
(field
|
||||||
|
name: (identifier) @variable.builtin)
|
||||||
|
|
||||||
|
(token
|
||||||
|
(identifier) @variable.parameter)
|
||||||
|
|
||||||
|
[
|
||||||
|
(brace_word)
|
||||||
|
(quote_word)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
[
|
||||||
|
(key_brace)
|
||||||
|
(key_paren)
|
||||||
|
] @attribute
|
||||||
|
|
||||||
|
(string
|
||||||
|
name: (identifier) @constant)
|
||||||
|
|
||||||
|
[
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
"," @punctuation.delimiter
|
@ -0,0 +1 @@
|
|||||||
|
; inherits: scheme
|
@ -0,0 +1 @@
|
|||||||
|
; inherits: scheme
|
@ -0,0 +1,66 @@
|
|||||||
|
[
|
||||||
|
"class"
|
||||||
|
"struct"
|
||||||
|
"module"
|
||||||
|
|
||||||
|
"def"
|
||||||
|
"alias"
|
||||||
|
"do"
|
||||||
|
"end"
|
||||||
|
|
||||||
|
"require"
|
||||||
|
"include"
|
||||||
|
"extend"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
"[" "]"
|
||||||
|
"(" ")"
|
||||||
|
"{" "}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
(operator) @operator
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
; literals
|
||||||
|
|
||||||
|
(nil) @constant.builtin
|
||||||
|
(bool) @constant.builtin.boolean
|
||||||
|
|
||||||
|
(integer) @constant.numeric.integer
|
||||||
|
(float) @constant.numeric.float
|
||||||
|
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(char)
|
||||||
|
(commandLiteral)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
(symbol) @string.special.symbol
|
||||||
|
|
||||||
|
(regex) @string.special.regex
|
||||||
|
|
||||||
|
; variables
|
||||||
|
|
||||||
|
(local_variable) @variable
|
||||||
|
|
||||||
|
[
|
||||||
|
(instance_variable)
|
||||||
|
(class_variable)
|
||||||
|
] @variable.other.member
|
||||||
|
|
||||||
|
(constant) @constant
|
||||||
|
|
||||||
|
; type defintitions
|
||||||
|
|
||||||
|
(type_identifier) @constructor
|
||||||
|
|
||||||
|
; method definition/call
|
||||||
|
(identifier) @function.method
|
||||||
|
|
||||||
|
; types
|
||||||
|
(generic_type) @type
|
||||||
|
(union_type) @type
|
||||||
|
(type_identifier) @type
|
||||||
|
|
@ -1,64 +1,85 @@
|
|||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
|
||||||
(tag_name) @tag
|
[
|
||||||
(nesting_selector) @tag
|
(tag_name)
|
||||||
(universal_selector) @tag
|
(nesting_selector)
|
||||||
|
(universal_selector)
|
||||||
|
] @tag
|
||||||
|
|
||||||
"~" @operator
|
[
|
||||||
">" @operator
|
"~"
|
||||||
"+" @operator
|
">"
|
||||||
"-" @operator
|
"+"
|
||||||
"*" @operator
|
"-"
|
||||||
"/" @operator
|
"*"
|
||||||
"=" @operator
|
"/"
|
||||||
"^=" @operator
|
"="
|
||||||
"|=" @operator
|
"^="
|
||||||
"~=" @operator
|
"|="
|
||||||
"$=" @operator
|
"~="
|
||||||
"*=" @operator
|
"$="
|
||||||
|
"*="
|
||||||
|
] @operator
|
||||||
|
|
||||||
"and" @operator
|
[
|
||||||
"or" @operator
|
"and"
|
||||||
"not" @operator
|
"not"
|
||||||
"only" @operator
|
"only"
|
||||||
|
"or"
|
||||||
(attribute_selector (plain_value) @string)
|
] @keyword.operator
|
||||||
(pseudo_element_selector (tag_name) @attribute)
|
|
||||||
(pseudo_class_selector (class_name) @attribute)
|
|
||||||
|
|
||||||
(class_name) @variable.other.member
|
|
||||||
(id_name) @variable.other.member
|
|
||||||
(namespace_name) @variable.other.member
|
|
||||||
(property_name) @variable.other.member
|
|
||||||
(feature_name) @variable.other.member
|
|
||||||
|
|
||||||
(attribute_name) @attribute
|
|
||||||
|
|
||||||
(function_name) @function
|
|
||||||
|
|
||||||
((property_name) @variable
|
((property_name) @variable
|
||||||
(#match? @variable "^--"))
|
(#match? @variable "^--"))
|
||||||
((plain_value) @variable
|
((plain_value) @variable
|
||||||
(#match? @variable "^--"))
|
(#match? @variable "^--"))
|
||||||
|
|
||||||
"@media" @keyword
|
(attribute_name) @attribute
|
||||||
"@import" @keyword
|
(class_name) @label
|
||||||
"@charset" @keyword
|
(feature_name) @variable.other.member
|
||||||
"@namespace" @keyword
|
(function_name) @function
|
||||||
"@supports" @keyword
|
(id_name) @label
|
||||||
"@keyframes" @keyword
|
(namespace_name) @namespace
|
||||||
(at_keyword) @keyword
|
(property_name) @variable.other.member
|
||||||
(to) @keyword
|
|
||||||
(from) @keyword
|
[
|
||||||
(important) @keyword
|
"@charset"
|
||||||
|
"@import"
|
||||||
|
"@keyframes"
|
||||||
|
"@media"
|
||||||
|
"@namespace"
|
||||||
|
"@supports"
|
||||||
|
(at_keyword)
|
||||||
|
(from)
|
||||||
|
(important)
|
||||||
|
(to)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
"#"
|
||||||
|
"."
|
||||||
|
] @punctuation
|
||||||
|
|
||||||
(string_value) @string
|
(string_value) @string
|
||||||
|
((color_value) "#") @string.special
|
||||||
(color_value) @string.special
|
(color_value) @string.special
|
||||||
|
|
||||||
(integer_value) @constant.numeric.integer
|
(integer_value) @constant.numeric.integer
|
||||||
(float_value) @constant.numeric.float
|
(float_value) @constant.numeric.float
|
||||||
(unit) @type
|
|
||||||
|
|
||||||
"#" @punctuation.delimiter
|
[
|
||||||
"," @punctuation.delimiter
|
")"
|
||||||
":" @punctuation.delimiter
|
"("
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
","
|
||||||
|
";"
|
||||||
|
":"
|
||||||
|
"::"
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
(plain_value) @constant
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
(comment) @comment.inside
|
||||||
|
|
||||||
|
[
|
||||||
|
(adt)
|
||||||
|
(decl_type)
|
||||||
|
(newtype)
|
||||||
|
] @class.around
|
||||||
|
|
||||||
|
((signature)? (function rhs:(_) @function.inside)) @function.around
|
||||||
|
(exp_lambda) @function.around
|
||||||
|
|
||||||
|
(adt (type_variable) @parameter.inside)
|
||||||
|
(patterns (_) @parameter.inside)
|
@ -1,2 +1,6 @@
|
|||||||
((line_comment) @injection.content
|
([
|
||||||
(#set! injection.language "comment"))
|
(comment)
|
||||||
|
(line_comment)
|
||||||
|
(block_comment)
|
||||||
|
(comment_environment)
|
||||||
|
] @injection.content (#set! injection.language "comment"))
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
; highlights.scm
|
||||||
|
|
||||||
|
function_keyword: (function_keyword) @keyword.function
|
||||||
|
|
||||||
|
(function_definition
|
||||||
|
function_name: (identifier) @function
|
||||||
|
(end) @function)
|
||||||
|
|
||||||
|
(parameter_list (identifier) @variable.parameter)
|
||||||
|
|
||||||
|
[
|
||||||
|
"if"
|
||||||
|
"elseif"
|
||||||
|
"else"
|
||||||
|
"switch"
|
||||||
|
"case"
|
||||||
|
"otherwise"
|
||||||
|
] @keyword.control.conditional
|
||||||
|
|
||||||
|
(if_statement (end) @keyword.control.conditional)
|
||||||
|
(switch_statement (end) @keyword.control.conditional)
|
||||||
|
|
||||||
|
["for" "while"] @keyword.control.repeat
|
||||||
|
(for_statement (end) @keyword.control.repeat)
|
||||||
|
(while_statement (end) @keyword.control.repeat)
|
||||||
|
|
||||||
|
["try" "catch"] @keyword.control.exception
|
||||||
|
(try_statement (end) @keyword.control.exception)
|
||||||
|
|
||||||
|
(function_definition end: (end) @keyword)
|
||||||
|
|
||||||
|
["return" "break" "continue"] @keyword.return
|
||||||
|
|
||||||
|
(
|
||||||
|
(identifier) @constant.builtin
|
||||||
|
(#any-of? @constant.builtin "true" "false")
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(identifier) @constant.builtin
|
||||||
|
(#eq? @constant.builtin "end")
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Punctuations
|
||||||
|
|
||||||
|
[";" ","] @punctuation.special
|
||||||
|
(argument_list "," @punctuation.delimiter)
|
||||||
|
(vector_definition ["," ";"] @punctuation.delimiter)
|
||||||
|
(cell_definition ["," ";"] @punctuation.delimiter)
|
||||||
|
":" @punctuation.delimiter
|
||||||
|
(parameter_list "," @punctuation.delimiter)
|
||||||
|
(return_value "," @punctuation.delimiter)
|
||||||
|
|
||||||
|
; ;; Brackets
|
||||||
|
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
;; Operators
|
||||||
|
"=" @operator
|
||||||
|
(operation [ ">"
|
||||||
|
"<"
|
||||||
|
"=="
|
||||||
|
"<="
|
||||||
|
">="
|
||||||
|
"=<"
|
||||||
|
"=>"
|
||||||
|
"~="
|
||||||
|
"*"
|
||||||
|
".*"
|
||||||
|
"/"
|
||||||
|
"\\"
|
||||||
|
"./"
|
||||||
|
"^"
|
||||||
|
".^"
|
||||||
|
"+"] @operator)
|
||||||
|
|
||||||
|
;; boolean operator
|
||||||
|
[
|
||||||
|
"&&"
|
||||||
|
"||"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
;; Number
|
||||||
|
(number) @constant.numeric
|
||||||
|
|
||||||
|
;; String
|
||||||
|
(string) @string
|
||||||
|
|
||||||
|
;; Comment
|
||||||
|
(comment) @comment
|
@ -0,0 +1,187 @@
|
|||||||
|
[
|
||||||
|
"sequenceDiagram"
|
||||||
|
"classDiagram"
|
||||||
|
"classDiagram-v2"
|
||||||
|
"stateDiagram"
|
||||||
|
"stateDiagram-v2"
|
||||||
|
"gantt"
|
||||||
|
"pie"
|
||||||
|
"flowchart"
|
||||||
|
"erdiagram"
|
||||||
|
|
||||||
|
"participant"
|
||||||
|
"as"
|
||||||
|
"activate"
|
||||||
|
"deactivate"
|
||||||
|
"note "
|
||||||
|
"over"
|
||||||
|
"link"
|
||||||
|
"links"
|
||||||
|
; "left of"
|
||||||
|
; "right of"
|
||||||
|
"properties"
|
||||||
|
"details"
|
||||||
|
"title"
|
||||||
|
"loop"
|
||||||
|
"rect"
|
||||||
|
"opt"
|
||||||
|
"alt"
|
||||||
|
"else"
|
||||||
|
"par"
|
||||||
|
"and"
|
||||||
|
"end"
|
||||||
|
(sequence_stmt_autonumber)
|
||||||
|
(note_placement_left)
|
||||||
|
(note_placement_right)
|
||||||
|
|
||||||
|
"class"
|
||||||
|
|
||||||
|
"state "
|
||||||
|
|
||||||
|
"dateformat"
|
||||||
|
"inclusiveenddates"
|
||||||
|
"topaxis"
|
||||||
|
"axisformat"
|
||||||
|
"includes"
|
||||||
|
"excludes"
|
||||||
|
"todaymarker"
|
||||||
|
"title"
|
||||||
|
"section"
|
||||||
|
|
||||||
|
"direction"
|
||||||
|
"subgraph"
|
||||||
|
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
(comment)
|
||||||
|
] @comment
|
||||||
|
|
||||||
|
(flow_vertex_id) @type
|
||||||
|
(flow_arrow_text) @label
|
||||||
|
(flow_text_literal) @string
|
||||||
|
|
||||||
|
[
|
||||||
|
":"
|
||||||
|
(sequence_signal_plus_sign)
|
||||||
|
(sequence_signal_minus_sign)
|
||||||
|
|
||||||
|
(class_visibility_public)
|
||||||
|
(class_visibility_private)
|
||||||
|
(class_visibility_protected)
|
||||||
|
(class_visibility_internal)
|
||||||
|
|
||||||
|
(state_division)
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
"-->"
|
||||||
|
(solid_arrow)
|
||||||
|
(dotted_arrow)
|
||||||
|
(solid_open_arrow)
|
||||||
|
(dotted_open_arrow)
|
||||||
|
(solid_cross)
|
||||||
|
(dotted_cross)
|
||||||
|
(solid_point)
|
||||||
|
(dotted_point)
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
[
|
||||||
|
(class_reltype_aggregation)
|
||||||
|
(class_reltype_extension)
|
||||||
|
(class_reltype_composition)
|
||||||
|
(class_reltype_dependency)
|
||||||
|
(class_linetype_solid)
|
||||||
|
(class_linetype_dotted)
|
||||||
|
"&"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
(sequence_actor) @variable
|
||||||
|
(sequence_text) @string
|
||||||
|
|
||||||
|
(class_name) @type
|
||||||
|
(class_label) @string
|
||||||
|
(class_method_line) @function.method
|
||||||
|
|
||||||
|
(state_name) @variable
|
||||||
|
|
||||||
|
(gantt_section) @markup.heading
|
||||||
|
(gantt_task_text) @variable.builtin
|
||||||
|
(gantt_task_data) @string
|
||||||
|
|
||||||
|
[
|
||||||
|
(class_annotation_line)
|
||||||
|
(class_stmt_annotation)
|
||||||
|
(class_generics)
|
||||||
|
|
||||||
|
(state_annotation_fork)
|
||||||
|
(state_annotation_join)
|
||||||
|
(state_annotation_choice)
|
||||||
|
] @type
|
||||||
|
|
||||||
|
(directive) @keyword.directive
|
||||||
|
|
||||||
|
(pie_label) @string
|
||||||
|
(pie_value) @constant.numeric
|
||||||
|
|
||||||
|
[
|
||||||
|
(flowchart_direction_lr)
|
||||||
|
(flowchart_direction_rl)
|
||||||
|
(flowchart_direction_tb)
|
||||||
|
(flowchart_direction_bt)
|
||||||
|
] @constant
|
||||||
|
|
||||||
|
(flow_vertex_id) @variable
|
||||||
|
|
||||||
|
[
|
||||||
|
(flow_link_arrow)
|
||||||
|
(flow_link_arrow_start)
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
(flow_link_arrowtext "|" @punctuation.bracket)
|
||||||
|
|
||||||
|
(flow_vertex_square [ "[" "]" ] @punctuation.bracket )
|
||||||
|
(flow_vertex_circle ["((" "))"] @punctuation.bracket )
|
||||||
|
(flow_vertex_ellipse ["(-" "-)"] @punctuation.bracket )
|
||||||
|
(flow_vertex_stadium ["([" "])"] @punctuation.bracket )
|
||||||
|
(flow_vertex_subroutine ["[[" "]]"] @punctuation.bracket )
|
||||||
|
(flow_vertex_rect ["[|" "|]"] @punctuation.bracket )
|
||||||
|
(flow_vertex_cylinder ["[(" ")]"] @punctuation.bracket )
|
||||||
|
(flow_vertex_round ["(" ")"] @punctuation.bracket )
|
||||||
|
(flow_vertex_diamond ["{" "}"] @punctuation.bracket )
|
||||||
|
(flow_vertex_hexagon ["{{" "}}"] @punctuation.bracket )
|
||||||
|
(flow_vertex_odd [">" "]"] @punctuation.bracket )
|
||||||
|
(flow_vertex_trapezoid ["[/" "\\]"] @punctuation.bracket )
|
||||||
|
(flow_vertex_inv_trapezoid ["[\\" "/]"] @punctuation.bracket )
|
||||||
|
(flow_vertex_leanright ["[/" "/]"] @punctuation.bracket )
|
||||||
|
(flow_vertex_leanleft ["[\\" "\\]"] @punctuation.bracket )
|
||||||
|
|
||||||
|
(flow_stmt_subgraph ["[" "]"] @punctuation.bracket )
|
||||||
|
|
||||||
|
[
|
||||||
|
(er_cardinarity_zero_or_one)
|
||||||
|
(er_cardinarity_zero_or_more)
|
||||||
|
(er_cardinarity_one_or_more)
|
||||||
|
(er_cardinarity_only_one)
|
||||||
|
(er_reltype_non_identifying)
|
||||||
|
(er_reltype_identifying)
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
(er_entity_name) @variable
|
||||||
|
|
||||||
|
(er_attribute_type) @type
|
||||||
|
(er_attribute_name) @variable
|
||||||
|
|
||||||
|
[
|
||||||
|
(er_attribute_key_type_pk)
|
||||||
|
(er_attribute_key_type_fk)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
(er_attribute_comment) @string
|
@ -0,0 +1 @@
|
|||||||
|
; inherits: scheme
|
@ -0,0 +1 @@
|
|||||||
|
; inherits: scheme
|
@ -1,2 +1,2 @@
|
|||||||
((comment) @injection.content
|
([(comment) (single_line_comment)] @injection.content
|
||||||
(#set! injection.language "comment"))
|
(#set! injection.language "comment"))
|
||||||
|