mirror of https://github.com/helix-editor/helix
Merge branch 'master' into sticky-context
commit
0fb8a87f7d
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
# Commands
|
||||
|
||||
Command mode can be activated by pressing `:`, similar to Vim. Built-in commands:
|
||||
Command mode can be activated by pressing `:`. The built-in commands are:
|
||||
|
||||
{{#include ./generated/typable-cmd.md}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Guides
|
||||
|
||||
This section contains guides for adding new language server configurations,
|
||||
tree-sitter grammars, textobject queries, etc.
|
||||
tree-sitter grammars, textobject queries, and other similar items.
|
||||
|
@ -1,45 +1,52 @@
|
||||
# Adding languages
|
||||
# Adding new languages to Helix
|
||||
|
||||
In order to add a new language to Helix, you will need to follow the steps
|
||||
below.
|
||||
|
||||
## Language configuration
|
||||
|
||||
To add a new language, you need to add a `[[language]]` entry to the
|
||||
`languages.toml` (see the [language configuration section]).
|
||||
1. Add a new `[[language]]` entry in the `languages.toml` file and provide the
|
||||
necessary configuration for the new language. For more information on
|
||||
language configuration, refer to the
|
||||
[language configuration section](../languages.md) of the documentation.
|
||||
2. If you are adding a new language or updating an existing language server
|
||||
configuration, run the command `cargo xtask docgen` to update the
|
||||
[Language Support](../lang-support.md) documentation.
|
||||
|
||||
When adding a new language or Language Server configuration for an existing
|
||||
language, run `cargo xtask docgen` to add the new configuration to the
|
||||
[Language Support][lang-support] docs before creating a pull request.
|
||||
When adding a Language Server configuration, be sure to update the
|
||||
[Language Server Wiki][install-lsp-wiki] with installation notes.
|
||||
> 💡 If you are adding a new Language Server configuration, make sure to update
|
||||
> the
|
||||
> [Language Server Wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
|
||||
> with the installation instructions.
|
||||
|
||||
## Grammar configuration
|
||||
|
||||
If a tree-sitter grammar is available for the language, add a new `[[grammar]]`
|
||||
entry to `languages.toml`.
|
||||
|
||||
You may use the `source.path` key rather than `source.git` with an absolute path
|
||||
to a locally available grammar for testing, but switch to `source.git` before
|
||||
submitting a pull request.
|
||||
1. If a tree-sitter grammar is available for the new language, add a new
|
||||
`[[grammar]]` entry to the `languages.toml` file.
|
||||
2. If you are testing the grammar locally, you can use the `source.path` key
|
||||
with an absolute path to the grammar. However, before submitting a pull
|
||||
request, make sure to switch to using `source.git`.
|
||||
|
||||
## Queries
|
||||
|
||||
For a language to have syntax-highlighting and indentation among
|
||||
other things, you have to add queries. Add a directory for your
|
||||
language with the path `runtime/queries/<name>/`. The tree-sitter
|
||||
[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
|
||||
gives more info on how to write queries.
|
||||
|
||||
> NOTE: When evaluating queries, the first matching query takes
|
||||
precedence, which is different from other editors like Neovim where
|
||||
the last matching query supersedes the ones before it. See
|
||||
[this issue][neovim-query-precedence] for an example.
|
||||
|
||||
## Common Issues
|
||||
|
||||
- If you get errors when running after switching branches, you may have to update the tree-sitter grammars. Run `hx --grammar fetch` to fetch the grammars and `hx --grammar build` to build any out-of-date grammars.
|
||||
|
||||
- If a parser is segfaulting or you want to remove the parser, make sure to remove the compiled parser in `runtime/grammar/<name>.so`
|
||||
|
||||
[language configuration section]: ../languages.md
|
||||
[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
|
||||
[install-lsp-wiki]: https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers
|
||||
[lang-support]: ../lang-support.md
|
||||
1. In order to provide syntax highlighting and indentation for the new language,
|
||||
you will need to add queries.
|
||||
2. Create a new directory for the language with the path
|
||||
`runtime/queries/<name>/`.
|
||||
3. Refer to the
|
||||
[tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
|
||||
for more information on writing queries.
|
||||
|
||||
> 💡 In Helix, the first matching query takes precedence when evaluating
|
||||
> queries, which is different from other editors such as Neovim where the last
|
||||
> matching query supersedes the ones before it. See
|
||||
> [this issue](https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090)
|
||||
> for an example.
|
||||
|
||||
## Common issues
|
||||
|
||||
- If you encounter errors when running Helix after switching branches, you may
|
||||
need to update the tree-sitter grammars. Run the command `hx --grammar fetch`
|
||||
to fetch the grammars and `hx --grammar build` to build any out-of-date
|
||||
grammars.
|
||||
- If a parser is causing a segfault, or you want to remove it, make sure to
|
||||
remove the compiled parser located at `runtime/grammars/<name>.so`.
|
||||
|
@ -1,180 +1,239 @@
|
||||
# Installation
|
||||
|
||||
We provide pre-built binaries on the [GitHub Releases page](https://github.com/helix-editor/helix/releases).
|
||||
# Installing Helix
|
||||
|
||||
<!--toc:start-->
|
||||
- [Pre-built binaries](#pre-built-binaries)
|
||||
- [Linux, macOS, Windows and OpenBSD packaging status](#linux-macos-windows-and-openbsd-packaging-status)
|
||||
- [Linux](#linux)
|
||||
- [Ubuntu](#ubuntu)
|
||||
- [Fedora/RHEL](#fedorarhel)
|
||||
- [Arch Linux community](#arch-linux-community)
|
||||
- [NixOS](#nixos)
|
||||
- [macOS](#macos)
|
||||
- [Homebrew Core](#homebrew-core)
|
||||
- [Windows](#windows)
|
||||
- [Scoop](#scoop)
|
||||
- [Chocolatey](#chocolatey)
|
||||
- [MSYS2](#msys2)
|
||||
- [Building from source](#building-from-source)
|
||||
- [Configuring Helix's runtime files](#configuring-helixs-runtime-files)
|
||||
- [Validating the installation](#validating-the-installation)
|
||||
- [Configure the desktop shortcut](#configure-the-desktop-shortcut)
|
||||
<!--toc:end-->
|
||||
|
||||
To install Helix, follow the instructions specific to your operating system.
|
||||
Note that:
|
||||
|
||||
- To get the latest nightly version of Helix, you need to
|
||||
[build from source](#building-from-source).
|
||||
|
||||
- To take full advantage of Helix, install the language servers for your
|
||||
preferred programming languages. See the
|
||||
[wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
|
||||
for instructions.
|
||||
|
||||
## Pre-built binaries
|
||||
|
||||
Download pre-built binaries from the
|
||||
[GitHub Releases page](https://github.com/helix-editor/helix/releases). Add the binary to your system's `$PATH` to use it from the command
|
||||
line.
|
||||
|
||||
## Linux, macOS, Windows and OpenBSD packaging status
|
||||
|
||||
Helix is available for Linux, macOS and Windows via the official repositories listed below.
|
||||
|
||||
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
|
||||
|
||||
## OSX
|
||||
## Linux
|
||||
|
||||
Helix is available in homebrew-core:
|
||||
The following third party repositories are available:
|
||||
|
||||
```
|
||||
brew install helix
|
||||
```
|
||||
### Ubuntu
|
||||
|
||||
## Linux
|
||||
Helix is available via [Maveonair's PPA](https://launchpad.net/~maveonair/+archive/ubuntu/helix-editor):
|
||||
|
||||
### NixOS
|
||||
```sh
|
||||
sudo add-apt-repository ppa:maveonair/helix-editor
|
||||
sudo apt update
|
||||
sudo apt install helix
|
||||
```
|
||||
|
||||
A [flake](https://nixos.wiki/wiki/Flakes) containing the package is available in
|
||||
the project root. The flake can also be used to spin up a reproducible development
|
||||
shell for working on Helix with `nix develop`.
|
||||
### Fedora/RHEL
|
||||
|
||||
Flake outputs are cached for each push to master using
|
||||
[Cachix](https://www.cachix.org/). The flake is configured to
|
||||
automatically make use of this cache assuming the user accepts
|
||||
the new settings on first use.
|
||||
Helix is available via `copr`:
|
||||
|
||||
If you are using a version of Nix without flakes enabled you can
|
||||
[install Cachix cli](https://docs.cachix.org/installation); `cachix use helix` will
|
||||
configure Nix to use cached outputs when possible.
|
||||
```sh
|
||||
sudo dnf copr enable varlad/helix
|
||||
sudo dnf install helix
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
### Arch Linux community
|
||||
|
||||
Releases are available in the `community` repository.
|
||||
Releases are available in the `community` repository:
|
||||
|
||||
A [helix-git](https://aur.archlinux.org/packages/helix-git/) package is also available on the AUR, which builds the master branch.
|
||||
```sh
|
||||
sudo pacman -S helix
|
||||
```
|
||||
Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available
|
||||
in the AUR, which builds the master branch.
|
||||
|
||||
### Fedora Linux
|
||||
### NixOS
|
||||
|
||||
You can install the COPR package for Helix via
|
||||
Helix is available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
|
||||
root. Use `nix develop` to spin up a reproducible development shell. Outputs are
|
||||
cached for each push to master using [Cachix](https://www.cachix.org/). The
|
||||
flake is configured to automatically make use of this cache assuming the user
|
||||
accepts the new settings on first use.
|
||||
|
||||
```
|
||||
sudo dnf copr enable varlad/helix
|
||||
sudo dnf install helix
|
||||
```
|
||||
If you are using a version of Nix without flakes enabled,
|
||||
[install Cachix CLI](https://docs.cachix.org/installation) and use
|
||||
`cachix use helix` to configure Nix to use cached outputs when possible.
|
||||
|
||||
## macOS
|
||||
|
||||
### Void Linux
|
||||
### Homebrew Core
|
||||
|
||||
```
|
||||
sudo xbps-install helix
|
||||
```sh
|
||||
brew install helix
|
||||
```
|
||||
|
||||
## Windows
|
||||
|
||||
Helix can be installed using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
|
||||
Install on Windows using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
|
||||
or [MSYS2](https://msys2.org/).
|
||||
|
||||
**Scoop:**
|
||||
### Scoop
|
||||
|
||||
```
|
||||
```sh
|
||||
scoop install helix
|
||||
```
|
||||
|
||||
**Chocolatey:**
|
||||
### Chocolatey
|
||||
|
||||
```
|
||||
```sh
|
||||
choco install helix
|
||||
```
|
||||
|
||||
**MSYS2:**
|
||||
|
||||
Choose the [proper command](https://www.msys2.org/docs/package-naming/) for your system from below:
|
||||
### MSYS2
|
||||
|
||||
- For 32 bit Windows 7 or above:
|
||||
For 64-bit Windows 8.1 or above:
|
||||
|
||||
```sh
|
||||
pacman -S mingw-w64-ucrt-x86_64-helix
|
||||
```
|
||||
pacman -S mingw-w64-i686-helix
|
||||
```
|
||||
|
||||
- For 64 bit Windows 7 or above:
|
||||
|
||||
```
|
||||
pacman -S mingw-w64-x86_64-helix
|
||||
```
|
||||
## Building from source
|
||||
|
||||
- For 64 bit Windows 8.1 or above:
|
||||
Clone the repository:
|
||||
|
||||
```
|
||||
pacman -S mingw-w64-ucrt-x86_64-helix
|
||||
```sh
|
||||
git clone https://github.com/helix-editor/helix
|
||||
cd helix
|
||||
```
|
||||
|
||||
## Build from source
|
||||
Compile from source:
|
||||
|
||||
```
|
||||
git clone https://github.com/helix-editor/helix
|
||||
cd helix
|
||||
```sh
|
||||
cargo install --path helix-term --locked
|
||||
```
|
||||
|
||||
This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars in `./runtime/grammars`.
|
||||
This command will create the `hx` executable and construct the tree-sitter
|
||||
grammars in the local `runtime` folder. To build the tree-sitter grammars requires
|
||||
a c++ compiler to be installed, for example `gcc-c++`.
|
||||
|
||||
If you are using the musl-libc instead of glibc the following environment variable must be set during the build
|
||||
to ensure tree sitter grammars can be loaded correctly:
|
||||
> 💡 If you are using the musl-libc instead of glibc the following environment variable must be set during the build
|
||||
> to ensure tree-sitter grammars can be loaded correctly:
|
||||
>
|
||||
> ```sh
|
||||
> RUSTFLAGS="-C target-feature=-crt-static"
|
||||
> ```
|
||||
|
||||
```
|
||||
RUSTFLAGS="-C target-feature=-crt-static"
|
||||
```
|
||||
> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
|
||||
> grammars with `hx --grammar fetch` (requires `git`) and compile them with
|
||||
> `hx --grammar build` (requires a C++ compiler). This will install them in
|
||||
> the `runtime` directory within the user's helix config directory (more
|
||||
> [details below](#multiple-runtime-directories)).
|
||||
|
||||
### Configuring Helix's runtime files
|
||||
|
||||
Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
|
||||
config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overridden
|
||||
via the `HELIX_RUNTIME` environment variable.
|
||||
#### Linux and macOS
|
||||
|
||||
| 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` |
|
||||
Either set the `HELIX_RUNTIME` environment variable to point to the runtime files and add it to your `~/.bashrc` or equivalent:
|
||||
|
||||
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.
|
||||
```sh
|
||||
HELIX_RUNTIME=/home/user-name/src/helix/runtime
|
||||
```
|
||||
|
||||
**PowerShell:**
|
||||
Or, create a symlink in `~/.config/helix` that links to the source code directory:
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"
|
||||
```sh
|
||||
ln -s $PWD/runtime ~/.config/helix/runtime
|
||||
```
|
||||
Note: "runtime" must be the absolute path to the runtime directory.
|
||||
|
||||
**Cmd:**
|
||||
#### Windows
|
||||
|
||||
Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
|
||||
`Edit environment variables for your account`) or use the `setx` command in
|
||||
Cmd:
|
||||
|
||||
```cmd
|
||||
cd %appdata%\helix
|
||||
mklink /D runtime "<helix-repo>\runtime"
|
||||
```sh
|
||||
setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime"
|
||||
```
|
||||
|
||||
The runtime location can be overridden via the `HELIX_RUNTIME` environment variable.
|
||||
> 💡 `%userprofile%` resolves to your user directory like
|
||||
> `C:\Users\Your-Name\` for example.
|
||||
|
||||
> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --path helix-term --locked`,
|
||||
> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`.
|
||||
Or, create a symlink in `%appdata%\helix\` that links to the source code directory:
|
||||
|
||||
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).
|
||||
| Method | Command |
|
||||
| ---------- | -------------------------------------------------------------------------------------- |
|
||||
| PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` |
|
||||
| Cmd | `cd %appdata%\helix` <br/> `mklink /D runtime "%userprofile%\src\helix\runtime"` |
|
||||
|
||||
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:
|
||||
> 💡 On Windows, creating a symbolic link may require running PowerShell or
|
||||
> Cmd as an administrator.
|
||||
|
||||
```bash
|
||||
cp contrib/Helix.desktop ~/.local/share/applications
|
||||
```
|
||||
#### Multiple runtime directories
|
||||
|
||||
To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
|
||||
When Helix finds multiple runtime directories it will search through them for files in the
|
||||
following order:
|
||||
|
||||
```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
|
||||
```
|
||||
1. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
|
||||
developing and testing helix only).
|
||||
2. `runtime/` subdirectory of OS-dependent helix user config directory.
|
||||
3. `$HELIX_RUNTIME`.
|
||||
4. `runtime/` subdirectory of path to Helix executable.
|
||||
|
||||
Please note: there is no icon for Helix yet, so the system default will be used.
|
||||
This order also sets the priority for selecting which file will be used if multiple runtime
|
||||
directories have files with the same name.
|
||||
|
||||
## Finishing up the installation
|
||||
### Validating the installation
|
||||
|
||||
To make sure everything is set up as expected you should finally run the helix healthcheck via
|
||||
To make sure everything is set up as expected you should run the Helix health
|
||||
check:
|
||||
|
||||
```
|
||||
```sh
|
||||
hx --health
|
||||
```
|
||||
|
||||
For more information on the information displayed in the health check results refer to [Healthcheck](https://github.com/helix-editor/helix/wiki/Healthcheck).
|
||||
For more information on the health check results refer to
|
||||
[Health check](https://github.com/helix-editor/helix/wiki/Healthcheck).
|
||||
|
||||
### Building tree-sitter grammars
|
||||
### Configure the desktop shortcut
|
||||
|
||||
Tree-sitter grammars must be fetched and compiled if not pre-packaged.
|
||||
Fetch grammars with `hx --grammar fetch` (requires `git`) and compile them
|
||||
with `hx --grammar build` (requires a C++ compiler).
|
||||
If your desktop environment supports the
|
||||
[XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html)
|
||||
you can configure Helix to show up in the application menu by copying the
|
||||
provided `.desktop` and icon files to their correct folders:
|
||||
|
||||
### Installing language servers
|
||||
```sh
|
||||
cp contrib/Helix.desktop ~/.local/share/applications
|
||||
cp contrib/helix.png ~/.icons # or ~/.local/share/icons
|
||||
```
|
||||
|
||||
To use another terminal than the system default, you can modify the `.desktop`
|
||||
file. For example, to use `kitty`:
|
||||
|
||||
Language servers can optionally be installed if you want their features (auto-complete, diagnostics etc.).
|
||||
Follow the [instructions on the wiki page](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers) to add your language servers of choice.
|
||||
```sh
|
||||
sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
|
||||
sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
|
||||
```
|
||||
|
@ -0,0 +1,525 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use helix_core::{smallvec, SmallVec};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum CaseChange {
|
||||
Upcase,
|
||||
Downcase,
|
||||
Capitalize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum FormatItem<'a> {
|
||||
Text(&'a str),
|
||||
Capture(usize),
|
||||
CaseChange(usize, CaseChange),
|
||||
Conditional(usize, Option<&'a str>, Option<&'a str>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Regex<'a> {
|
||||
value: &'a str,
|
||||
replacement: Vec<FormatItem<'a>>,
|
||||
options: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SnippetElement<'a> {
|
||||
Tabstop {
|
||||
tabstop: usize,
|
||||
},
|
||||
Placeholder {
|
||||
tabstop: usize,
|
||||
value: Vec<SnippetElement<'a>>,
|
||||
},
|
||||
Choice {
|
||||
tabstop: usize,
|
||||
choices: Vec<&'a str>,
|
||||
},
|
||||
Variable {
|
||||
name: &'a str,
|
||||
default: Option<&'a str>,
|
||||
regex: Option<Regex<'a>>,
|
||||
},
|
||||
Text(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Snippet<'a> {
|
||||
elements: Vec<SnippetElement<'a>>,
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Result<Snippet<'_>> {
|
||||
parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
|
||||
}
|
||||
|
||||
fn render_elements(
|
||||
snippet_elements: &[SnippetElement<'_>],
|
||||
insert: &mut String,
|
||||
offset: &mut usize,
|
||||
tabstops: &mut Vec<(usize, (usize, usize))>,
|
||||
newline_with_offset: &String,
|
||||
include_placeholer: bool,
|
||||
) {
|
||||
use SnippetElement::*;
|
||||
|
||||
for element in snippet_elements {
|
||||
match element {
|
||||
&Text(text) => {
|
||||
// small optimization to avoid calling replace when it's unnecessary
|
||||
let text = if text.contains('\n') {
|
||||
Cow::Owned(text.replace('\n', newline_with_offset))
|
||||
} else {
|
||||
Cow::Borrowed(text)
|
||||
};
|
||||
*offset += text.chars().count();
|
||||
insert.push_str(&text);
|
||||
}
|
||||
&Variable {
|
||||
name: _,
|
||||
regex: _,
|
||||
r#default,
|
||||
} => {
|
||||
// TODO: variables. For now, fall back to the default, which defaults to "".
|
||||
let text = r#default.unwrap_or_default();
|
||||
*offset += text.chars().count();
|
||||
insert.push_str(text);
|
||||
}
|
||||
&Tabstop { tabstop } => {
|
||||
tabstops.push((tabstop, (*offset, *offset)));
|
||||
}
|
||||
Placeholder {
|
||||
tabstop,
|
||||
value: inner_snippet_elements,
|
||||
} => {
|
||||
let start_offset = *offset;
|
||||
if include_placeholer {
|
||||
render_elements(
|
||||
inner_snippet_elements,
|
||||
insert,
|
||||
offset,
|
||||
tabstops,
|
||||
newline_with_offset,
|
||||
include_placeholer,
|
||||
);
|
||||
}
|
||||
tabstops.push((*tabstop, (start_offset, *offset)));
|
||||
}
|
||||
&Choice {
|
||||
tabstop,
|
||||
choices: _,
|
||||
} => {
|
||||
// TODO: choices
|
||||
tabstops.push((tabstop, (*offset, *offset)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)] // only used one time
|
||||
pub fn render(
|
||||
snippet: &Snippet<'_>,
|
||||
newline_with_offset: String,
|
||||
include_placeholer: bool,
|
||||
) -> (String, Vec<SmallVec<[(usize, usize); 1]>>) {
|
||||
let mut insert = String::new();
|
||||
let mut tabstops = Vec::new();
|
||||
let mut offset = 0;
|
||||
|
||||
render_elements(
|
||||
&snippet.elements,
|
||||
&mut insert,
|
||||
&mut offset,
|
||||
&mut tabstops,
|
||||
&newline_with_offset,
|
||||
include_placeholer,
|
||||
);
|
||||
|
||||
// sort in ascending order (except for 0, which should always be the last one (per lsp doc))
|
||||
tabstops.sort_unstable_by_key(|(n, _)| if *n == 0 { usize::MAX } else { *n });
|
||||
|
||||
// merge tabstops with the same index (we take advantage of the fact that we just sorted them
|
||||
// above to simply look backwards)
|
||||
let mut ntabstops = Vec::<SmallVec<[(usize, usize); 1]>>::new();
|
||||
{
|
||||
let mut prev = None;
|
||||
for (tabstop, r) in tabstops {
|
||||
if prev == Some(tabstop) {
|
||||
let len_1 = ntabstops.len() - 1;
|
||||
ntabstops[len_1].push(r);
|
||||
} else {
|
||||
prev = Some(tabstop);
|
||||
ntabstops.push(smallvec![r]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(insert, ntabstops)
|
||||
}
|
||||
|
||||
mod parser {
|
||||
use helix_parsec::*;
|
||||
|
||||
use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement};
|
||||
|
||||
/*
|
||||
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax
|
||||
|
||||
any ::= tabstop | placeholder | choice | variable | text
|
||||
tabstop ::= '$' int | '${' int '}'
|
||||
placeholder ::= '${' int ':' any '}'
|
||||
choice ::= '${' int '|' text (',' text)* '|}'
|
||||
variable ::= '$' var | '${' var }'
|
||||
| '${' var ':' any '}'
|
||||
| '${' var '/' regex '/' (format | text)+ '/' options '}'
|
||||
format ::= '$' int | '${' int '}'
|
||||
| '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
|
||||
| '${' int ':+' if '}'
|
||||
| '${' int ':?' if ':' else '}'
|
||||
| '${' int ':-' else '}' | '${' int ':' else '}'
|
||||
regex ::= Regular Expression value (ctor-string)
|
||||
options ::= Regular Expression option (ctor-options)
|
||||
var ::= [_a-zA-Z] [_a-zA-Z0-9]*
|
||||
int ::= [0-9]+
|
||||
text ::= .*
|
||||
if ::= text
|
||||
else ::= text
|
||||
*/
|
||||
|
||||
fn var<'a>() -> impl Parser<'a, Output = &'a str> {
|
||||
// var = [_a-zA-Z][_a-zA-Z0-9]*
|
||||
move |input: &'a str| match input
|
||||
.char_indices()
|
||||
.take_while(|(p, c)| {
|
||||
*c == '_'
|
||||
|| if *p == 0 {
|
||||
c.is_ascii_alphabetic()
|
||||
} else {
|
||||
c.is_ascii_alphanumeric()
|
||||
}
|
||||
})
|
||||
.last()
|
||||
{
|
||||
Some((index, c)) if index >= 1 => {
|
||||
let index = index + c.len_utf8();
|
||||
Ok((&input[index..], &input[0..index]))
|
||||
}
|
||||
_ => Err(input),
|
||||
}
|
||||
}
|
||||
|
||||
fn text<'a, const SIZE: usize>(cs: [char; SIZE]) -> impl Parser<'a, Output = &'a str> {
|
||||
take_while(move |c| cs.into_iter().all(|c1| c != c1))
|
||||
}
|
||||
|
||||
fn digit<'a>() -> impl Parser<'a, Output = usize> {
|
||||
filter_map(take_while(|c| c.is_ascii_digit()), |s| s.parse().ok())
|
||||
}
|
||||
|
||||
fn case_change<'a>() -> impl Parser<'a, Output = CaseChange> {
|
||||
use CaseChange::*;
|
||||
|
||||
choice!(
|
||||
map("upcase", |_| Upcase),
|
||||
map("downcase", |_| Downcase),
|
||||
map("capitalize", |_| Capitalize),
|
||||
)
|
||||
}
|
||||
|
||||
fn format<'a>() -> impl Parser<'a, Output = FormatItem<'a>> {
|
||||
use FormatItem::*;
|
||||
|
||||
choice!(
|
||||
// '$' int
|
||||
map(right("$", digit()), Capture),
|
||||
// '${' int '}'
|
||||
map(seq!("${", digit(), "}"), |seq| Capture(seq.1)),
|
||||
// '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
|
||||
map(seq!("${", digit(), ":/", case_change(), "}"), |seq| {
|
||||
CaseChange(seq.1, seq.3)
|
||||
}),
|
||||
// '${' int ':+' if '}'
|
||||
map(
|
||||
seq!("${", digit(), ":+", take_until(|c| c == '}'), "}"),
|
||||
|seq| { Conditional(seq.1, Some(seq.3), None) }
|
||||
),
|
||||
// '${' int ':?' if ':' else '}'
|
||||
map(
|
||||
seq!(
|
||||
"${",
|
||||
digit(),
|
||||
":?",
|
||||
take_until(|c| c == ':'),
|
||||
":",
|
||||
take_until(|c| c == '}'),
|
||||
"}"
|
||||
),
|
||||
|seq| { Conditional(seq.1, Some(seq.3), Some(seq.5)) }
|
||||
),
|
||||
// '${' int ':-' else '}' | '${' int ':' else '}'
|
||||
map(
|
||||
seq!(
|
||||
"${",
|
||||
digit(),
|
||||
":",
|
||||
optional("-"),
|
||||
take_until(|c| c == '}'),
|
||||
"}"
|
||||
),
|
||||
|seq| { Conditional(seq.1, None, Some(seq.4)) }
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn regex<'a>() -> impl Parser<'a, Output = Regex<'a>> {
|
||||
let text = map(text(['$', '/']), FormatItem::Text);
|
||||
let replacement = reparse_as(
|
||||
take_until(|c| c == '/'),
|
||||
one_or_more(choice!(format(), text)),
|
||||
);
|
||||
|
||||
map(
|
||||
seq!(
|
||||
"/",
|
||||
take_until(|c| c == '/'),
|
||||
"/",
|
||||
replacement,
|
||||
"/",
|
||||
optional(take_until(|c| c == '}')),
|
||||
),
|
||||
|(_, value, _, replacement, _, options)| Regex {
|
||||
value,
|
||||
replacement,
|
||||
options,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn tabstop<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
|
||||
map(
|
||||
or(
|
||||
right("$", digit()),
|
||||
map(seq!("${", digit(), "}"), |values| values.1),
|
||||
),
|
||||
|digit| SnippetElement::Tabstop { tabstop: digit },
|
||||
)
|
||||
}
|
||||
|
||||
fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
|
||||
let text = map(text(['$', '}']), SnippetElement::Text);
|
||||
map(
|
||||
seq!(
|
||||
"${",
|
||||
digit(),
|
||||
":",
|
||||
one_or_more(choice!(anything(), text)),
|
||||
"}"
|
||||
),
|
||||
|seq| SnippetElement::Placeholder {
|
||||
tabstop: seq.1,
|
||||
value: seq.3,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn choice<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
|
||||
map(
|
||||
seq!(
|
||||
"${",
|
||||
digit(),
|
||||
"|",
|
||||
sep(take_until(|c| c == ',' || c == '|'), ","),
|
||||
"|}",
|
||||
),
|
||||
|seq| SnippetElement::Choice {
|
||||
tabstop: seq.1,
|
||||
choices: seq.3,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn variable<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
|
||||
choice!(
|
||||
// $var
|
||||
map(right("$", var()), |name| SnippetElement::Variable {
|
||||
name,
|
||||
default: None,
|
||||
regex: None,
|
||||
}),
|
||||
// ${var:default}
|
||||
map(
|
||||
seq!("${", var(), ":", take_until(|c| c == '}'), "}",),
|
||||
|values| SnippetElement::Variable {
|
||||
name: values.1,
|
||||
default: Some(values.3),
|
||||
regex: None,
|
||||
}
|
||||
),
|
||||
// ${var/value/format/options}
|
||||
map(seq!("${", var(), regex(), "}"), |values| {
|
||||
SnippetElement::Variable {
|
||||
name: values.1,
|
||||
default: None,
|
||||
regex: Some(values.2),
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
|
||||
// The parser has to be constructed lazily to avoid infinite opaque type recursion
|
||||
|input: &'a str| {
|
||||
let parser = choice!(tabstop(), placeholder(), choice(), variable());
|
||||
parser.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
|
||||
let text = map(text(['$']), SnippetElement::Text);
|
||||
map(one_or_more(choice!(anything(), text)), |parts| Snippet {
|
||||
elements: parts,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Result<Snippet, &str> {
|
||||
snippet().parse(s).map(|(_input, elements)| elements)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::SnippetElement::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_string_is_error() {
|
||||
assert_eq!(Err(""), parse(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_placeholders_in_function_call() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
elements: vec![
|
||||
Text("match("),
|
||||
Placeholder {
|
||||
tabstop: 1,
|
||||
value: vec!(Text("Arg1")),
|
||||
},
|
||||
Text(")")
|
||||
]
|
||||
}),
|
||||
parse("match(${1:Arg1})")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_placeholders_in_statement() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
elements: vec![
|
||||
Text("local "),
|
||||
Placeholder {
|
||||
tabstop: 1,
|
||||
value: vec!(Text("var")),
|
||||
},
|
||||
Text(" = "),
|
||||
Placeholder {
|
||||
tabstop: 1,
|
||||
value: vec!(Text("value")),
|
||||
},
|
||||
]
|
||||
}),
|
||||
parse("local ${1:var} = ${1:value}")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tabstop_nested_in_placeholder() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
elements: vec![Placeholder {
|
||||
tabstop: 1,
|
||||
value: vec!(Text("var, "), Tabstop { tabstop: 2 },),
|
||||
},]
|
||||
}),
|
||||
parse("${1:var, $2}")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_placeholder_nested_in_placeholder() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
elements: vec![Placeholder {
|
||||
tabstop: 1,
|
||||
value: vec!(
|
||||
Text("foo "),
|
||||
Placeholder {
|
||||
tabstop: 2,
|
||||
value: vec!(Text("bar")),
|
||||
},
|
||||
),
|
||||
},]
|
||||
}),
|
||||
parse("${1:foo ${2:bar}}")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_all() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
elements: vec![
|
||||
Text("hello "),
|
||||
Tabstop { tabstop: 1 },
|
||||
Tabstop { tabstop: 2 },
|
||||
Text(" "),
|
||||
Choice {
|
||||
tabstop: 1,
|
||||
choices: vec!["one", "two", "three"]
|
||||
},
|
||||
Text(" "),
|
||||
Variable {
|
||||
name: "name",
|
||||
default: Some("foo"),
|
||||
regex: None
|
||||
},
|
||||
Text(" "),
|
||||
Variable {
|
||||
name: "var",
|
||||
default: None,
|
||||
regex: None
|
||||
},
|
||||
Text(" "),
|
||||
Variable {
|
||||
name: "TM",
|
||||
default: None,
|
||||
regex: None
|
||||
},
|
||||
]
|
||||
}),
|
||||
parse("hello $1${2} ${1|one,two,three|} ${name:foo} $var $TM")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_capture_replace() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
elements: vec![Variable {
|
||||
name: "TM_FILENAME",
|
||||
default: None,
|
||||
regex: Some(Regex {
|
||||
value: "(.*).+$",
|
||||
replacement: vec![FormatItem::Capture(1)],
|
||||
options: None,
|
||||
}),
|
||||
}]
|
||||
}),
|
||||
parse("${TM_FILENAME/(.*).+$/$1/}")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "helix-parsec"
|
||||
version = "0.6.0"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
description = "Parser combinators for Helix"
|
||||
categories = ["editor"]
|
||||
repository = "https://github.com/helix-editor/helix"
|
||||
homepage = "https://helix-editor.com"
|
||||
include = ["src/**/*", "README.md"]
|
||||
|
||||
[dependencies]
|
@ -0,0 +1,561 @@
|
||||
//! Parser-combinator functions
|
||||
//!
|
||||
//! This module provides parsers and parser combinators which can be used
|
||||
//! together to build parsers by functional composition.
|
||||
|
||||
// This module implements parser combinators following https://bodil.lol/parser-combinators/.
|
||||
// `sym` (trait implementation for `&'static str`), `map`, `pred` (filter), `one_or_more`,
|
||||
// `zero_or_more`, as well as the `Parser` trait originate mostly from that post.
|
||||
// The remaining parsers and parser combinators are either based on
|
||||
// https://github.com/archseer/snippets.nvim/blob/a583da6ef130d2a4888510afd8c4e5ffd62d0dce/lua/snippet/parser.lua#L5-L138
|
||||
// or are novel.
|
||||
|
||||
// When a parser matches the input successfully, it returns `Ok((next_input, some_value))`
|
||||
// where the type of the returned value depends on the parser. If the parser fails to match,
|
||||
// it returns `Err(input)`.
|
||||
type ParseResult<'a, Output> = Result<(&'a str, Output), &'a str>;
|
||||
|
||||
/// A parser or parser-combinator.
|
||||
///
|
||||
/// Parser-combinators compose multiple parsers together to parse input.
|
||||
/// For example, two basic parsers (`&'static str`s) may be combined with
|
||||
/// a parser-combinator like [or] to produce a new parser.
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{or, Parser};
|
||||
/// let foo = "foo"; // matches "foo" literally
|
||||
/// let bar = "bar"; // matches "bar" literally
|
||||
/// let foo_or_bar = or(foo, bar); // matches either "foo" or "bar"
|
||||
/// assert_eq!(Ok(("", "foo")), foo_or_bar.parse("foo"));
|
||||
/// assert_eq!(Ok(("", "bar")), foo_or_bar.parse("bar"));
|
||||
/// assert_eq!(Err("baz"), foo_or_bar.parse("baz"));
|
||||
/// ```
|
||||
pub trait Parser<'a> {
|
||||
type Output;
|
||||
|
||||
fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output>;
|
||||
}
|
||||
|
||||
// Most parser-combinators are written as higher-order functions which take some
|
||||
// parser(s) as input and return a new parser: a function that takes input and returns
|
||||
// a parse result. The underlying implementation of [Parser::parse] for these functions
|
||||
// is simply application.
|
||||
#[doc(hidden)]
|
||||
impl<'a, F, T> Parser<'a> for F
|
||||
where
|
||||
F: Fn(&'a str) -> ParseResult<T>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
|
||||
self(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser which matches the string literal exactly.
|
||||
///
|
||||
/// This parser succeeds if the next characters in the input are equal to the given
|
||||
/// string literal.
|
||||
///
|
||||
/// Note that [str::parse] interferes with calling [Parser::parse] on string literals
|
||||
/// directly; this trait implementation works when used within any parser combinator
|
||||
/// but does not work on its own. To call [Parser::parse] on a parser for a string
|
||||
/// literal, use the [token] parser.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{or, Parser};
|
||||
/// let parser = or("foo", "bar");
|
||||
/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
|
||||
/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
|
||||
/// assert_eq!(Err("baz"), parser.parse("baz"));
|
||||
/// ```
|
||||
impl<'a> Parser<'a> for &'static str {
|
||||
type Output = &'a str;
|
||||
|
||||
fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
|
||||
match input.get(0..self.len()) {
|
||||
Some(actual) if actual == *self => Ok((&input[self.len()..], &input[0..self.len()])),
|
||||
_ => Err(input),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parsers
|
||||
|
||||
/// A parser which matches the given string literally.
|
||||
///
|
||||
/// This function is a convenience for interpreting string literals as parsers
|
||||
/// and is only necessary to avoid conflict with [str::parse]. See the documentation
|
||||
/// for the `&'static str` implementation of [Parser].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{token, Parser};
|
||||
/// let parser = token("foo");
|
||||
/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
|
||||
/// assert_eq!(Err("bar"), parser.parse("bar"));
|
||||
/// ```
|
||||
pub fn token<'a>(literal: &'static str) -> impl Parser<'a, Output = &'a str> {
|
||||
literal
|
||||
}
|
||||
|
||||
/// A parser which matches all values until the specified pattern is found.
|
||||
///
|
||||
/// If the pattern is not found, this parser does not match. The input up to the
|
||||
/// character which returns `true` is returned but not that character itself.
|
||||
///
|
||||
/// If the pattern function returns true on the first input character, this
|
||||
/// parser fails.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{take_until, Parser};
|
||||
/// let parser = take_until(|c| c == '.');
|
||||
/// assert_eq!(Ok((".bar", "foo")), parser.parse("foo.bar"));
|
||||
/// assert_eq!(Err(".foo"), parser.parse(".foo"));
|
||||
/// assert_eq!(Err("foo"), parser.parse("foo"));
|
||||
/// ```
|
||||
pub fn take_until<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str>
|
||||
where
|
||||
F: Fn(char) -> bool,
|
||||
{
|
||||
move |input: &'a str| match input.find(&pattern) {
|
||||
Some(index) if index != 0 => Ok((&input[index..], &input[0..index])),
|
||||
_ => Err(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser which matches all values until the specified pattern no longer match.
|
||||
///
|
||||
/// This parser only ever fails if the input has a length of zero.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{take_while, Parser};
|
||||
/// let parser = take_while(|c| c == '1');
|
||||
/// assert_eq!(Ok(("2", "11")), parser.parse("112"));
|
||||
/// assert_eq!(Err("22"), parser.parse("22"));
|
||||
/// ```
|
||||
pub fn take_while<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str>
|
||||
where
|
||||
F: Fn(char) -> bool,
|
||||
{
|
||||
move |input: &'a str| match input
|
||||
.char_indices()
|
||||
.take_while(|(_p, c)| pattern(*c))
|
||||
.last()
|
||||
{
|
||||
Some((index, c)) => {
|
||||
let index = index + c.len_utf8();
|
||||
Ok((&input[index..], &input[0..index]))
|
||||
}
|
||||
_ => Err(input),
|
||||
}
|
||||
}
|
||||
|
||||
// Variadic parser combinators
|
||||
|
||||
/// A parser combinator which matches a sequence of parsers in an all-or-nothing fashion.
|
||||
///
|
||||
/// The returned value is a tuple containing the outputs of all parsers in order. Each
|
||||
/// parser in the sequence may be typed differently.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{seq, Parser};
|
||||
/// let parser = seq!("<", "a", ">");
|
||||
/// assert_eq!(Ok(("", ("<", "a", ">"))), parser.parse("<a>"));
|
||||
/// assert_eq!(Err("<b>"), parser.parse("<b>"));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! seq {
|
||||
($($parsers: expr),+ $(,)?) => {
|
||||
($($parsers),+)
|
||||
}
|
||||
}
|
||||
|
||||
// Seq is implemented using trait-implementations of Parser for various size tuples.
|
||||
// This allows sequences to be typed heterogeneously.
|
||||
macro_rules! seq_impl {
|
||||
($($parser:ident),+) => {
|
||||
#[allow(non_snake_case)]
|
||||
impl<'a, $($parser),+> Parser<'a> for ($($parser),+)
|
||||
where
|
||||
$($parser: Parser<'a>),+
|
||||
{
|
||||
type Output = ($($parser::Output),+);
|
||||
|
||||
fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
|
||||
let ($($parser),+) = self;
|
||||
seq_body_impl!(input, input, $($parser),+ ; )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! seq_body_impl {
|
||||
($input:expr, $next_input:expr, $head:ident, $($tail:ident),+ ; $(,)? $($acc:ident),*) => {
|
||||
match $head.parse($next_input) {
|
||||
Ok((next_input, $head)) => seq_body_impl!($input, next_input, $($tail),+ ; $($acc),*, $head),
|
||||
Err(_) => Err($input),
|
||||
}
|
||||
};
|
||||
($input:expr, $next_input:expr, $last:ident ; $(,)? $($acc:ident),*) => {
|
||||
match $last.parse($next_input) {
|
||||
Ok((next_input, last)) => Ok((next_input, ($($acc),+, last))),
|
||||
Err(_) => Err($input),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seq_impl!(A, B);
|
||||
seq_impl!(A, B, C);
|
||||
seq_impl!(A, B, C, D);
|
||||
seq_impl!(A, B, C, D, E);
|
||||
seq_impl!(A, B, C, D, E, F);
|
||||
seq_impl!(A, B, C, D, E, F, G);
|
||||
seq_impl!(A, B, C, D, E, F, G, H);
|
||||
seq_impl!(A, B, C, D, E, F, G, H, I);
|
||||
seq_impl!(A, B, C, D, E, F, G, H, I, J);
|
||||
|
||||
/// A parser combinator which chooses the first of the input parsers which matches
|
||||
/// successfully.
|
||||
///
|
||||
/// All input parsers must have the same output type. This is a variadic form for [or].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{choice, or, Parser};
|
||||
/// let parser = choice!("foo", "bar", "baz");
|
||||
/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
|
||||
/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
|
||||
/// assert_eq!(Err("quiz"), parser.parse("quiz"));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! choice {
|
||||
($parser: expr $(,)?) => {
|
||||
$parser
|
||||
};
|
||||
($parser: expr, $($rest: expr),+ $(,)?) => {
|
||||
or($parser, choice!($($rest),+))
|
||||
}
|
||||
}
|
||||
|
||||
// Ordinary parser combinators
|
||||
|
||||
/// A parser combinator which takes a parser as input and maps the output using the
|
||||
/// given transformation function.
|
||||
///
|
||||
/// This corresponds to [Result::map]. The value is only mapped if the input parser
|
||||
/// matches against input.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{map, Parser};
|
||||
/// let parser = map("123", |s| s.parse::<i32>().unwrap());
|
||||
/// assert_eq!(Ok(("", 123)), parser.parse("123"));
|
||||
/// assert_eq!(Err("abc"), parser.parse("abc"));
|
||||
/// ```
|
||||
pub fn map<'a, P, F, T>(parser: P, map_fn: F) -> impl Parser<'a, Output = T>
|
||||
where
|
||||
P: Parser<'a>,
|
||||
F: Fn(P::Output) -> T,
|
||||
{
|
||||
move |input| {
|
||||
parser
|
||||
.parse(input)
|
||||
.map(|(next_input, result)| (next_input, map_fn(result)))
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser combinator which succeeds if the given parser matches the input and
|
||||
/// the given `filter_map_fn` returns `Some`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{filter_map, take_until, Parser};
|
||||
/// let parser = filter_map(take_until(|c| c == '.'), |s| s.parse::<i32>().ok());
|
||||
/// assert_eq!(Ok((".456", 123)), parser.parse("123.456"));
|
||||
/// assert_eq!(Err("abc.def"), parser.parse("abc.def"));
|
||||
/// ```
|
||||
pub fn filter_map<'a, P, F, T>(parser: P, filter_map_fn: F) -> impl Parser<'a, Output = T>
|
||||
where
|
||||
P: Parser<'a>,
|
||||
F: Fn(P::Output) -> Option<T>,
|
||||
{
|
||||
move |input| match parser.parse(input) {
|
||||
Ok((next_input, value)) => match filter_map_fn(value) {
|
||||
Some(value) => Ok((next_input, value)),
|
||||
None => Err(input),
|
||||
},
|
||||
Err(_) => Err(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser combinator which succeeds if the first given parser matches the input and
|
||||
/// the second given parse also matches.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{reparse_as, take_until, one_or_more, Parser};
|
||||
/// let parser = reparse_as(take_until(|c| c == '/'), one_or_more("a"));
|
||||
/// assert_eq!(Ok(("/bb", vec!["a", "a"])), parser.parse("aa/bb"));
|
||||
/// ```
|
||||
pub fn reparse_as<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T>
|
||||
where
|
||||
P1: Parser<'a, Output = &'a str>,
|
||||
P2: Parser<'a, Output = T>,
|
||||
{
|
||||
filter_map(parser1, move |str| {
|
||||
parser2.parse(str).map(|(_, value)| value).ok()
|
||||
})
|
||||
}
|
||||
|
||||
/// A parser combinator which only matches the input when the predicate function
|
||||
/// returns true.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{filter, take_until, Parser};
|
||||
/// let parser = filter(take_until(|c| c == '.'), |s| s == &"123");
|
||||
/// assert_eq!(Ok((".456", "123")), parser.parse("123.456"));
|
||||
/// assert_eq!(Err("456.123"), parser.parse("456.123"));
|
||||
/// ```
|
||||
pub fn filter<'a, P, F, T>(parser: P, pred_fn: F) -> impl Parser<'a, Output = T>
|
||||
where
|
||||
P: Parser<'a, Output = T>,
|
||||
F: Fn(&P::Output) -> bool,
|
||||
{
|
||||
move |input| {
|
||||
if let Ok((next_input, value)) = parser.parse(input) {
|
||||
if pred_fn(&value) {
|
||||
return Ok((next_input, value));
|
||||
}
|
||||
}
|
||||
Err(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser combinator which matches either of the input parsers.
|
||||
///
|
||||
/// Both parsers must have the same output type. For a variadic form which
|
||||
/// can take any number of parsers, use `choice!`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{or, Parser};
|
||||
/// let parser = or("foo", "bar");
|
||||
/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
|
||||
/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
|
||||
/// assert_eq!(Err("baz"), parser.parse("baz"));
|
||||
/// ```
|
||||
pub fn or<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T>
|
||||
where
|
||||
P1: Parser<'a, Output = T>,
|
||||
P2: Parser<'a, Output = T>,
|
||||
{
|
||||
move |input| match parser1.parse(input) {
|
||||
ok @ Ok(_) => ok,
|
||||
Err(_) => parser2.parse(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser combinator which attempts to match the given parser, returning a
|
||||
/// `None` output value if the parser does not match.
|
||||
///
|
||||
/// The parser produced with this combinator always succeeds. If the given parser
|
||||
/// succeeds, `Some(value)` is returned where `value` is the output of the given
|
||||
/// parser. Otherwise, `None`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{optional, Parser};
|
||||
/// let parser = optional("foo");
|
||||
/// assert_eq!(Ok(("bar", Some("foo"))), parser.parse("foobar"));
|
||||
/// assert_eq!(Ok(("bar", None)), parser.parse("bar"));
|
||||
/// ```
|
||||
pub fn optional<'a, P, T>(parser: P) -> impl Parser<'a, Output = Option<T>>
|
||||
where
|
||||
P: Parser<'a, Output = T>,
|
||||
{
|
||||
move |input| match parser.parse(input) {
|
||||
Ok((next_input, value)) => Ok((next_input, Some(value))),
|
||||
Err(_) => Ok((input, None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser combinator which runs the given parsers in sequence and returns the
|
||||
/// value of `left` if both are matched.
|
||||
///
|
||||
/// This is useful for two-element sequences in which you only want the output
|
||||
/// value of the `left` parser.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{left, Parser};
|
||||
/// let parser = left("foo", "bar");
|
||||
/// assert_eq!(Ok(("", "foo")), parser.parse("foobar"));
|
||||
/// ```
|
||||
pub fn left<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T>
|
||||
where
|
||||
L: Parser<'a, Output = T>,
|
||||
R: Parser<'a>,
|
||||
{
|
||||
map(seq!(left, right), |(left_value, _)| left_value)
|
||||
}
|
||||
|
||||
/// A parser combinator which runs the given parsers in sequence and returns the
|
||||
/// value of `right` if both are matched.
|
||||
///
|
||||
/// This is useful for two-element sequences in which you only want the output
|
||||
/// value of the `right` parser.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{right, Parser};
|
||||
/// let parser = right("foo", "bar");
|
||||
/// assert_eq!(Ok(("", "bar")), parser.parse("foobar"));
|
||||
/// ```
|
||||
pub fn right<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T>
|
||||
where
|
||||
L: Parser<'a>,
|
||||
R: Parser<'a, Output = T>,
|
||||
{
|
||||
map(seq!(left, right), |(_, right_value)| right_value)
|
||||
}
|
||||
|
||||
/// A parser combinator which matches the given parser against the input zero or
|
||||
/// more times.
|
||||
///
|
||||
/// This parser always succeeds and returns the empty Vec when it matched zero
|
||||
/// times.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{zero_or_more, Parser};
|
||||
/// let parser = zero_or_more("a");
|
||||
/// assert_eq!(Ok(("", vec![])), parser.parse(""));
|
||||
/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a"));
|
||||
/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa"));
|
||||
/// assert_eq!(Ok(("bb", vec![])), parser.parse("bb"));
|
||||
/// ```
|
||||
pub fn zero_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
|
||||
where
|
||||
P: Parser<'a, Output = T>,
|
||||
{
|
||||
move |mut input| {
|
||||
let mut values = Vec::new();
|
||||
|
||||
while let Ok((next_input, value)) = parser.parse(input) {
|
||||
input = next_input;
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
Ok((input, values))
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser combinator which matches the given parser against the input one or
|
||||
/// more times.
|
||||
///
|
||||
/// This parser combinator acts the same as [zero_or_more] but must match at
|
||||
/// least once.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use helix_parsec::{one_or_more, Parser};
|
||||
/// let parser = one_or_more("a");
|
||||
/// assert_eq!(Err(""), parser.parse(""));
|
||||
/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a"));
|
||||
/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa"));
|
||||
/// assert_eq!(Err("bb"), parser.parse("bb"));
|
||||
/// ```
|
||||
pub fn one_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
|
||||
where
|
||||
P: Parser<'a, Output = T>,
|
||||
{
|
||||
move |mut input| {
|
||||
let mut values = Vec::new();
|
||||
|
||||
match parser.parse(input) {
|
||||
Ok((next_input, value)) => {
|
||||
input = next_input;
|
||||
values.push(value);
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
while let Ok((next_input, value)) = parser.parse(input) {
|
||||
input = next_input;
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
Ok((input, values))
|
||||
}
|
||||
}
|
||||
|
||||
/// A parser combinator which matches one or more instances of the given parser
|
||||
/// interspersed with the separator parser.
|
||||
///
|
||||
/// Output values of the separator parser are discarded.
|
||||
///
|
||||
/// This is typically used to parse function arguments or list items.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use helix_parsec::{sep, Parser};
|
||||
/// let parser = sep("a", ",");
|
||||
/// assert_eq!(Ok(("", vec!["a", "a", "a"])), parser.parse("a,a,a"));
|
||||
/// ```
|
||||
pub fn sep<'a, P, S, T>(parser: P, separator: S) -> impl Parser<'a, Output = Vec<T>>
|
||||
where
|
||||
P: Parser<'a, Output = T>,
|
||||
S: Parser<'a>,
|
||||
{
|
||||
move |mut input| {
|
||||
let mut values = Vec::new();
|
||||
|
||||
match parser.parse(input) {
|
||||
Ok((next_input, value)) => {
|
||||
input = next_input;
|
||||
values.push(value);
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
loop {
|
||||
match separator.parse(input) {
|
||||
Ok((next_input, _)) => input = next_input,
|
||||
Err(_) => break,
|
||||
}
|
||||
|
||||
match parser.parse(input) {
|
||||
Ok((next_input, value)) => {
|
||||
input = next_input;
|
||||
values.push(value);
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok((input, values))
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
(line_comment) @comment.inside
|
||||
(line_comment)+ @comment.around
|
||||
(block_comment) @comment.inside
|
||||
(block_comment)+ @comment.around
|
||||
|
||||
((type_annotation)?
|
||||
(value_declaration
|
||||
(function_declaration_left (lower_case_identifier))
|
||||
(eq)
|
||||
(_) @function.inside
|
||||
)
|
||||
) @function.around
|
||||
|
||||
(parenthesized_expr
|
||||
(anonymous_function_expr
|
||||
(
|
||||
(arrow)
|
||||
(_) @function.inside
|
||||
)
|
||||
)
|
||||
) @function.around
|
||||
|
||||
(value_declaration
|
||||
(function_declaration_left
|
||||
(lower_pattern
|
||||
(lower_case_identifier) @parameter.inside @parameter.around
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(value_declaration
|
||||
(function_declaration_left
|
||||
(pattern) @parameter.inside @parameter.around
|
||||
)
|
||||
)
|
||||
|
||||
(value_declaration
|
||||
(function_declaration_left
|
||||
(tuple_pattern
|
||||
(pattern) @parameter.inside
|
||||
) @parameter.around
|
||||
)
|
||||
)
|
||||
|
||||
(value_declaration
|
||||
(function_declaration_left
|
||||
(record_pattern
|
||||
(lower_pattern
|
||||
(lower_case_identifier) @parameter.inside
|
||||
)
|
||||
) @parameter.around
|
||||
)
|
||||
)
|
||||
|
||||
(parenthesized_expr
|
||||
(anonymous_function_expr
|
||||
(
|
||||
(backslash)
|
||||
(pattern) @parameter.inside
|
||||
(arrow)
|
||||
)
|
||||
)
|
||||
)
|
@ -1,2 +1,18 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
|
||||
; ((section) @injection.content
|
||||
; (#set! injection.language "comment"))
|
||||
|
||||
((section
|
||||
(attribute
|
||||
(identifier) @_type
|
||||
(string) @_is_shader)
|
||||
(property
|
||||
(path) @_is_code
|
||||
(string) @injection.content))
|
||||
(#match? @_type "type")
|
||||
(#match? @_is_shader "Shader")
|
||||
(#eq? @_is_code "code")
|
||||
(#set! injection.language "glsl")
|
||||
)
|
||||
|
@ -1,2 +1,4 @@
|
||||
|
||||
((html_tag) @injection.content (#set! injection.language "html") (#set! injection.include-unnamed-children))
|
||||
|
||||
((latex_block) @injection.content (#set! injection.language "latex") (#set! injection.include-unnamed-children))
|
||||
|
@ -0,0 +1,126 @@
|
||||
(comment) @comment
|
||||
|
||||
(label) @label
|
||||
|
||||
(preproc_expression) @keyword.directive
|
||||
|
||||
[
|
||||
(line_here_token)
|
||||
(section_here_token)
|
||||
] @variable.builtin
|
||||
|
||||
(unary_expression
|
||||
operator: _ @operator)
|
||||
(binary_expression
|
||||
operator: _ @operator)
|
||||
(conditional_expression
|
||||
"?" @operator
|
||||
":" @operator)
|
||||
|
||||
[
|
||||
":"
|
||||
","
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
(instruction_prefix) @keyword
|
||||
(actual_instruction
|
||||
instruction: (word) @function)
|
||||
|
||||
(call_syntax_expression
|
||||
base: (word) @function)
|
||||
|
||||
(size_hint) @type
|
||||
(struc_declaration
|
||||
name: (word) @type)
|
||||
(struc_instance
|
||||
name: (word) @type)
|
||||
|
||||
(effective_address
|
||||
hint: _ @type)
|
||||
(effective_address
|
||||
segment: _ @constant.builtin)
|
||||
|
||||
(register) @constant.builtin
|
||||
|
||||
(number_literal) @constant.numeric.integer
|
||||
(string_literal) @string
|
||||
(float_literal) @constant.numeric.float
|
||||
(packed_bcd_literal) @constant.numeric.integer
|
||||
|
||||
((word) @constant
|
||||
(#match? @constant "^[A-Z_][?A-Z_0-9]+$"))
|
||||
((word) @constant.builtin
|
||||
(#match? @constant.builtin "^__\\?[A-Z_a-z0-9]+\\?__$"))
|
||||
(word) @variable
|
||||
|
||||
(preproc_arg) @keyword.directive
|
||||
|
||||
[
|
||||
(preproc_def)
|
||||
(preproc_function_def)
|
||||
(preproc_undef)
|
||||
(preproc_alias)
|
||||
(preproc_multiline_macro)
|
||||
(preproc_multiline_unmacro)
|
||||
(preproc_if)
|
||||
(preproc_rotate)
|
||||
(preproc_rep_loop)
|
||||
(preproc_include)
|
||||
(preproc_pathsearch)
|
||||
(preproc_depend)
|
||||
(preproc_use)
|
||||
(preproc_push)
|
||||
(preproc_pop)
|
||||
(preproc_repl)
|
||||
(preproc_arg)
|
||||
(preproc_stacksize)
|
||||
(preproc_local)
|
||||
(preproc_reporting)
|
||||
(preproc_pragma)
|
||||
(preproc_line)
|
||||
(preproc_clear)
|
||||
] @keyword.directive
|
||||
[
|
||||
(pseudo_instruction_dx)
|
||||
(pseudo_instruction_resx)
|
||||
(pseudo_instruction_incbin_command)
|
||||
(pseudo_instruction_equ_command)
|
||||
(pseudo_instruction_times_prefix)
|
||||
(pseudo_instruction_alignx_macro)
|
||||
] @function.special
|
||||
[
|
||||
(assembl_directive_target)
|
||||
(assembl_directive_defaults)
|
||||
(assembl_directive_sections)
|
||||
(assembl_directive_absolute)
|
||||
(assembl_directive_symbols)
|
||||
(assembl_directive_common)
|
||||
(assembl_directive_symbolfixes)
|
||||
(assembl_directive_cpu)
|
||||
(assembl_directive_floathandling)
|
||||
(assembl_directive_org)
|
||||
(assembl_directive_sectalign)
|
||||
|
||||
(assembl_directive_primitive_target)
|
||||
(assembl_directive_primitive_defaults)
|
||||
(assembl_directive_primitive_sections)
|
||||
(assembl_directive_primitive_absolute)
|
||||
(assembl_directive_primitive_symbols)
|
||||
(assembl_directive_primitive_common)
|
||||
(assembl_directive_primitive_symbolfixes)
|
||||
(assembl_directive_primitive_cpu)
|
||||
(assembl_directive_primitive_floathandling)
|
||||
(assembl_directive_primitive_org)
|
||||
(assembl_directive_primitive_sectalign)
|
||||
(assembl_directive_primitive_warning)
|
||||
(assembl_directive_primitive_map)
|
||||
] @keyword
|
@ -0,0 +1,2 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
@ -0,0 +1,15 @@
|
||||
(preproc_multiline_macro
|
||||
body: (body) @function.inside) @function.around
|
||||
(struc_declaration
|
||||
body: (struc_declaration_body) @class.inside) @class.around
|
||||
(struc_instance
|
||||
body: (struc_instance_body) @class.inside) @class.around
|
||||
|
||||
(preproc_function_def_parameters
|
||||
(word) @parameter.inside)
|
||||
(call_syntax_arguments
|
||||
(_) @parameter.inside)
|
||||
(operand) @parameter.inside
|
||||
|
||||
(comment) @comment.inside
|
||||
(comment)+ @comment.around
|
@ -0,0 +1,136 @@
|
||||
[
|
||||
(keyword_from)
|
||||
(keyword_filter)
|
||||
(keyword_derive)
|
||||
(keyword_group)
|
||||
(keyword_aggregate)
|
||||
(keyword_sort)
|
||||
(keyword_take)
|
||||
(keyword_window)
|
||||
(keyword_join)
|
||||
(keyword_select)
|
||||
(keyword_switch)
|
||||
(keyword_append)
|
||||
(keyword_remove)
|
||||
(keyword_intersect)
|
||||
(keyword_rolling)
|
||||
(keyword_rows)
|
||||
(keyword_expanding)
|
||||
(keyword_let)
|
||||
(keyword_prql)
|
||||
(keyword_from_text)
|
||||
] @keyword
|
||||
|
||||
(literal) @string
|
||||
|
||||
(assignment
|
||||
alias: (field) @variable.other.member)
|
||||
|
||||
alias: (identifier) @variable.other.member
|
||||
|
||||
(f_string) @string.special
|
||||
(s_string) @string.special
|
||||
|
||||
(comment) @comment
|
||||
|
||||
(keyword_func) @keyword.function
|
||||
|
||||
(function_call
|
||||
(identifier) @function)
|
||||
|
||||
[
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"/"
|
||||
"="
|
||||
"=="
|
||||
"<"
|
||||
"<="
|
||||
"!="
|
||||
">="
|
||||
">"
|
||||
"->"
|
||||
(bang)
|
||||
] @operator
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
","
|
||||
"."
|
||||
(pipe)
|
||||
] @punctuation.delimiter
|
||||
|
||||
(literal
|
||||
(integer) @constant.numeric.integer)
|
||||
|
||||
(integer) @constant.numeric.integer
|
||||
|
||||
(literal
|
||||
(decimal_number) @constant.numeric.float)
|
||||
|
||||
(decimal_number) @constant.numeric.float
|
||||
|
||||
[
|
||||
(keyword_min)
|
||||
(keyword_max)
|
||||
(keyword_count)
|
||||
(keyword_count_distinct)
|
||||
(keyword_average)
|
||||
(keyword_avg)
|
||||
(keyword_sum)
|
||||
(keyword_stddev)
|
||||
(keyword_count)
|
||||
] @function
|
||||
|
||||
[
|
||||
(keyword_side)
|
||||
(keyword_version)
|
||||
(keyword_target)
|
||||
(keyword_null)
|
||||
(keyword_format)
|
||||
] @attribute
|
||||
|
||||
(target) @function.builtin
|
||||
|
||||
[
|
||||
(date)
|
||||
(time)
|
||||
(timestamp)
|
||||
] @string.special
|
||||
|
||||
[
|
||||
(keyword_left)
|
||||
(keyword_inner)
|
||||
(keyword_right)
|
||||
(keyword_full)
|
||||
(keyword_csv)
|
||||
(keyword_json)
|
||||
] @function.method
|
||||
|
||||
[
|
||||
(keyword_true)
|
||||
(keyword_false)
|
||||
] @constant.builtin.boolean
|
||||
|
||||
[
|
||||
(keyword_and)
|
||||
(keyword_or)
|
||||
] @keyword.operator
|
||||
|
||||
(function_definition
|
||||
(keyword_func)
|
||||
name: (identifier) @function)
|
||||
|
||||
(parameter
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(variable
|
||||
(keyword_let)
|
||||
name: (identifier) @constant)
|
@ -0,0 +1,8 @@
|
||||
((s_string) @injection.content
|
||||
(#set! injection.language "sql"))
|
||||
|
||||
(from_text
|
||||
(keyword_from_text)
|
||||
(keyword_json)
|
||||
(literal) @injection.content
|
||||
(#set! injection.language "json"))
|
@ -0,0 +1,38 @@
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(title)
|
||||
] @markup.heading.1
|
||||
|
||||
[
|
||||
"adornment"
|
||||
] @markup.heading.marker
|
||||
|
||||
[
|
||||
(target)
|
||||
(reference)
|
||||
] @markup.link.url
|
||||
|
||||
[
|
||||
"bullet"
|
||||
] @markup.list.unnumbered
|
||||
|
||||
(strong) @markup.bold
|
||||
(emphasis) @markup.italic
|
||||
(literal) @markup.raw.inline
|
||||
|
||||
(list_item
|
||||
(term) @markup.bold
|
||||
(classifier)? @markup.italic)
|
||||
|
||||
(directive
|
||||
[".." (type) "::"] @function
|
||||
)
|
||||
|
||||
(field
|
||||
[":" (field_name) ":"] @variable.other.member
|
||||
)
|
||||
|
||||
(interpreted_text) @markup.raw.inline
|
||||
|
||||
(interpreted_text (role)) @keyword
|
@ -0,0 +1,336 @@
|
||||
; -------
|
||||
; Tree-Sitter doesn't allow overrides in regards to captures,
|
||||
; though it is possible to affect the child node of a captured
|
||||
; node. Thus, the approach here is to flip the order so that
|
||||
; overrides are unnecessary.
|
||||
; -------
|
||||
|
||||
; -------
|
||||
; Types
|
||||
; -------
|
||||
|
||||
; ---
|
||||
; Primitives
|
||||
; ---
|
||||
|
||||
(escape_sequence) @constant.character.escape
|
||||
(primitive_type) @type.builtin
|
||||
(boolean_literal) @constant.builtin.boolean
|
||||
(integer_literal) @constant.numeric.integer
|
||||
(float_literal) @constant.numeric.float
|
||||
(char_literal) @constant.character
|
||||
[
|
||||
(string_literal)
|
||||
(raw_string_literal)
|
||||
] @string
|
||||
[
|
||||
(line_comment)
|
||||
(block_comment)
|
||||
] @comment
|
||||
|
||||
; ---
|
||||
; Extraneous
|
||||
; ---
|
||||
|
||||
(self) @variable.builtin
|
||||
(enum_variant (identifier) @type.enum.variant)
|
||||
|
||||
(field_initializer
|
||||
(field_identifier) @variable.other.member)
|
||||
(shorthand_field_initializer
|
||||
(identifier) @variable.other.member)
|
||||
(shorthand_field_identifier) @variable.other.member
|
||||
|
||||
(loop_label
|
||||
"'" @label
|
||||
(identifier) @label)
|
||||
|
||||
; ---
|
||||
; Punctuation
|
||||
; ---
|
||||
|
||||
[
|
||||
"::"
|
||||
"."
|
||||
";"
|
||||
","
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"#"
|
||||
] @punctuation.bracket
|
||||
(type_arguments
|
||||
[
|
||||
"<"
|
||||
">"
|
||||
] @punctuation.bracket)
|
||||
(type_parameters
|
||||
[
|
||||
"<"
|
||||
">"
|
||||
] @punctuation.bracket)
|
||||
(closure_parameters
|
||||
"|" @punctuation.bracket)
|
||||
|
||||
; ---
|
||||
; Variables
|
||||
; ---
|
||||
|
||||
(let_declaration
|
||||
pattern: [
|
||||
((identifier) @variable)
|
||||
((tuple_pattern
|
||||
(identifier) @variable))
|
||||
])
|
||||
|
||||
; It needs to be anonymous to not conflict with `call_expression` further below.
|
||||
(_
|
||||
value: (field_expression
|
||||
value: (identifier)? @variable
|
||||
field: (field_identifier) @variable.other.member))
|
||||
|
||||
(parameter
|
||||
pattern: (identifier) @variable.parameter)
|
||||
(closure_parameters
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
; -------
|
||||
; Keywords
|
||||
; -------
|
||||
|
||||
(for_expression
|
||||
"for" @keyword.control.repeat)
|
||||
((identifier) @keyword.control
|
||||
(#match? @keyword.control "^yield$"))
|
||||
|
||||
"in" @keyword.control
|
||||
|
||||
[
|
||||
"match"
|
||||
"if"
|
||||
"else"
|
||||
] @keyword.control.conditional
|
||||
|
||||
[
|
||||
"while"
|
||||
] @keyword.control.repeat
|
||||
|
||||
[
|
||||
"break"
|
||||
"continue"
|
||||
"return"
|
||||
] @keyword.control.return
|
||||
|
||||
[
|
||||
"contract"
|
||||
"script"
|
||||
"predicate"
|
||||
] @keyword.other
|
||||
|
||||
"use" @keyword.control.import
|
||||
(dep_item "dep" @keyword.control.import !body)
|
||||
(use_as_clause "as" @keyword.control.import)
|
||||
|
||||
(type_cast_expression "as" @keyword.operator)
|
||||
|
||||
[
|
||||
"as"
|
||||
"pub"
|
||||
"dep"
|
||||
|
||||
"abi"
|
||||
"impl"
|
||||
"where"
|
||||
"trait"
|
||||
"for"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"struct"
|
||||
"enum"
|
||||
"storage"
|
||||
"configurable"
|
||||
] @keyword.storage.type
|
||||
|
||||
"let" @keyword.storage
|
||||
"fn" @keyword.function
|
||||
"abi" @keyword.function
|
||||
|
||||
(mutable_specifier) @keyword.storage.modifier.mut
|
||||
|
||||
(reference_type "&" @keyword.storage.modifier.ref)
|
||||
(self_parameter "&" @keyword.storage.modifier.ref)
|
||||
|
||||
[
|
||||
"const"
|
||||
"ref"
|
||||
"deref"
|
||||
"move"
|
||||
] @keyword.storage.modifier
|
||||
|
||||
; TODO: variable.mut to highlight mutable identifiers via locals.scm
|
||||
|
||||
; -------
|
||||
; Guess Other Types
|
||||
; -------
|
||||
|
||||
((identifier) @constant
|
||||
(#match? @constant "^[A-Z][A-Z\\d_]*$"))
|
||||
|
||||
; ---
|
||||
; PascalCase identifiers in call_expressions (e.g. `Ok()`)
|
||||
; are assumed to be enum constructors.
|
||||
; ---
|
||||
|
||||
(call_expression
|
||||
function: [
|
||||
((identifier) @type.enum.variant
|
||||
(#match? @type.enum.variant "^[A-Z]"))
|
||||
(scoped_identifier
|
||||
name: ((identifier) @type.enum.variant
|
||||
(#match? @type.enum.variant "^[A-Z]")))
|
||||
])
|
||||
|
||||
; ---
|
||||
; Assume that types in match arms are enums and not
|
||||
; tuple structs. Same for `if let` expressions.
|
||||
; ---
|
||||
|
||||
(match_pattern
|
||||
(scoped_identifier
|
||||
name: (identifier) @constructor))
|
||||
(tuple_struct_pattern
|
||||
type: [
|
||||
((identifier) @constructor)
|
||||
(scoped_identifier
|
||||
name: (identifier) @constructor)
|
||||
])
|
||||
(struct_pattern
|
||||
type: [
|
||||
((type_identifier) @constructor)
|
||||
(scoped_type_identifier
|
||||
name: (type_identifier) @constructor)
|
||||
])
|
||||
|
||||
; ---
|
||||
; Other PascalCase identifiers are assumed to be structs.
|
||||
; ---
|
||||
|
||||
((identifier) @type
|
||||
(#match? @type "^[A-Z]"))
|
||||
|
||||
; -------
|
||||
; Functions
|
||||
; -------
|
||||
|
||||
(call_expression
|
||||
function: [
|
||||
((identifier) @function)
|
||||
(scoped_identifier
|
||||
name: (identifier) @function)
|
||||
(field_expression
|
||||
field: (field_identifier) @function)
|
||||
])
|
||||
(generic_function
|
||||
function: [
|
||||
((identifier) @function)
|
||||
(scoped_identifier
|
||||
name: (identifier) @function)
|
||||
(field_expression
|
||||
field: (field_identifier) @function.method)
|
||||
])
|
||||
|
||||
(function_item
|
||||
name: (identifier) @function)
|
||||
|
||||
(function_signature_item
|
||||
name: (identifier) @function)
|
||||
|
||||
; -------
|
||||
; Operators
|
||||
; -------
|
||||
|
||||
[
|
||||
"*"
|
||||
"'"
|
||||
"->"
|
||||
"=>"
|
||||
"<="
|
||||
"="
|
||||
"=="
|
||||
"!"
|
||||
"!="
|
||||
"%"
|
||||
"%="
|
||||
"&"
|
||||
"&="
|
||||
"&&"
|
||||
"|"
|
||||
"|="
|
||||
"||"
|
||||
"^"
|
||||
"^="
|
||||
"*"
|
||||
"*="
|
||||
"-"
|
||||
"-="
|
||||
"+"
|
||||
"+="
|
||||
"/"
|
||||
"/="
|
||||
">"
|
||||
"<"
|
||||
">="
|
||||
">>"
|
||||
"<<"
|
||||
">>="
|
||||
"<<="
|
||||
"@"
|
||||
".."
|
||||
"..="
|
||||
"'"
|
||||
] @operator
|
||||
|
||||
; -------
|
||||
; Paths
|
||||
; -------
|
||||
|
||||
(use_declaration
|
||||
argument: (identifier) @namespace)
|
||||
(use_wildcard
|
||||
(identifier) @namespace)
|
||||
(dep_item
|
||||
name: (identifier) @namespace)
|
||||
(scoped_use_list
|
||||
path: (identifier)? @namespace)
|
||||
(use_list
|
||||
(identifier) @namespace)
|
||||
(use_as_clause
|
||||
path: (identifier)? @namespace
|
||||
alias: (identifier) @namespace)
|
||||
|
||||
; ---
|
||||
; Remaining Paths
|
||||
; ---
|
||||
|
||||
(scoped_identifier
|
||||
path: (identifier)? @namespace
|
||||
name: (identifier) @namespace)
|
||||
(scoped_type_identifier
|
||||
path: (identifier) @namespace)
|
||||
|
||||
; -------
|
||||
; Remaining Identifiers
|
||||
; -------
|
||||
|
||||
"?" @special
|
||||
|
||||
(type_identifier) @type
|
||||
(identifier) @variable
|
||||
(field_identifier) @variable.other.member
|
@ -0,0 +1,71 @@
|
||||
[
|
||||
(use_list)
|
||||
(block)
|
||||
(match_block)
|
||||
(arguments)
|
||||
(parameters)
|
||||
(declaration_list)
|
||||
(field_declaration_list)
|
||||
(field_initializer_list)
|
||||
(struct_pattern)
|
||||
(tuple_pattern)
|
||||
(unit_expression)
|
||||
(enum_variant_list)
|
||||
(call_expression)
|
||||
(binary_expression)
|
||||
(field_expression)
|
||||
(tuple_expression)
|
||||
(array_expression)
|
||||
(where_clause)
|
||||
|
||||
(token_tree)
|
||||
] @indent
|
||||
|
||||
[
|
||||
"}"
|
||||
"]"
|
||||
")"
|
||||
] @outdent
|
||||
|
||||
; Indent the right side of assignments.
|
||||
; The #not-same-line? predicate is required to prevent an extra indent for e.g.
|
||||
; an else-clause where the previous if-clause starts on the same line as the assignment.
|
||||
(assignment_expression
|
||||
.
|
||||
(_) @expr-start
|
||||
right: (_) @indent
|
||||
(#not-same-line? @indent @expr-start)
|
||||
(#set! "scope" "all")
|
||||
)
|
||||
(compound_assignment_expr
|
||||
.
|
||||
(_) @expr-start
|
||||
right: (_) @indent
|
||||
(#not-same-line? @indent @expr-start)
|
||||
(#set! "scope" "all")
|
||||
)
|
||||
(let_declaration
|
||||
.
|
||||
(_) @expr-start
|
||||
value: (_) @indent
|
||||
alternative: (_)? @indent
|
||||
(#not-same-line? @indent @expr-start)
|
||||
(#set! "scope" "all")
|
||||
)
|
||||
(if_expression
|
||||
.
|
||||
(_) @expr-start
|
||||
condition: (_) @indent
|
||||
(#not-same-line? @indent @expr-start)
|
||||
(#set! "scope" "all")
|
||||
)
|
||||
|
||||
; Some field expressions where the left part is a multiline expression are not
|
||||
; indented by cargo fmt.
|
||||
; Because this multiline expression might be nested in an arbitrary number of
|
||||
; field expressions, this can only be matched using a Regex.
|
||||
(field_expression
|
||||
value: (_) @val
|
||||
"." @outdent
|
||||
(#match? @val "(\\A[^\\n\\r]+\\([\\t ]*(\\n|\\r).*)|(\\A[^\\n\\r]*\\{[\\t ]*(\\n|\\r))")
|
||||
)
|
@ -0,0 +1,2 @@
|
||||
([(line_comment) (block_comment)] @injection.content
|
||||
(#set! injection.language "comment"))
|
@ -0,0 +1,17 @@
|
||||
; Scopes
|
||||
|
||||
[
|
||||
(function_item)
|
||||
(closure_expression)
|
||||
(block)
|
||||
] @local.scope
|
||||
|
||||
; Definitions
|
||||
|
||||
(parameter
|
||||
(identifier) @local.definition)
|
||||
|
||||
(closure_parameters (identifier) @local.definition)
|
||||
|
||||
; References
|
||||
(identifier) @local.reference
|
@ -0,0 +1,52 @@
|
||||
(function_item
|
||||
body: (_) @function.inside) @function.around(closure_expression body: (_) @function.inside) @function.around
|
||||
|
||||
(struct_item
|
||||
body: (_) @class.inside) @class.around
|
||||
|
||||
(enum_item
|
||||
body: (_) @class.inside) @class.around
|
||||
|
||||
(trait_item
|
||||
body: (_) @class.inside) @class.around
|
||||
|
||||
(impl_item
|
||||
body: (_) @class.inside) @class.around
|
||||
|
||||
(parameters
|
||||
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
|
||||
|
||||
(type_parameters
|
||||
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
|
||||
|
||||
(type_arguments
|
||||
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
|
||||
|
||||
(closure_parameters
|
||||
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
|
||||
|
||||
(arguments
|
||||
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
|
||||
|
||||
[
|
||||
(line_comment)
|
||||
(block_comment)
|
||||
] @comment.inside
|
||||
|
||||
(line_comment)+ @comment.around
|
||||
|
||||
(block_comment) @comment.around
|
||||
|
||||
(; #[test]
|
||||
(attribute_item
|
||||
(attribute
|
||||
(identifier) @_test_attribute))
|
||||
; allow other attributes like #[should_panic] and comments
|
||||
[
|
||||
(attribute_item)
|
||||
(line_comment)
|
||||
]*
|
||||
; the test function
|
||||
(function_item
|
||||
body: (_) @test.inside) @test.around
|
||||
(#eq? @_test_attribute "test"))
|
@ -0,0 +1,169 @@
|
||||
# Author: Mofiqul Islam <mofi0islam@gmail.com>
|
||||
|
||||
"attribute" = "orange_4"
|
||||
|
||||
"type" = "teal_2"
|
||||
"type.builtin" = "teal_2"
|
||||
|
||||
"constructor" = "blue_2"
|
||||
|
||||
"constant" = "violet_2"
|
||||
"constant.builtin" = { fg = "violet_2", modifiers = ["bold"] }
|
||||
"constant.character" = "teal_3"
|
||||
"constant.numeric" = { fg = "teal_3", modifiers = ["bold"] }
|
||||
"constant.character.escape" = "violet_2"
|
||||
|
||||
"string" = "teal_2"
|
||||
"string.regexp" = "purple_2"
|
||||
"string.special" = "blue_2"
|
||||
|
||||
"comment" = "dark_2"
|
||||
|
||||
"variable" = "light_4"
|
||||
"variable.parameter" = "orange_2"
|
||||
"variable.builtin" = "orange_2"
|
||||
"variable.other" = "teal_2"
|
||||
"variable.other.member" = "teal_2"
|
||||
|
||||
"label" = "purple_2"
|
||||
|
||||
"punctuation" = "light_4"
|
||||
"punctuation.delimiter" = "light_4"
|
||||
"punctuation.bracket" = "light_4"
|
||||
"punctuation.special" = "red_3"
|
||||
|
||||
"keyword" = { fg = "orange_2", modifiers = ["bold"] }
|
||||
"keyword.control" = { fg = "orange_2", modifiers = ["bold"] }
|
||||
"keyword.operator" = "purple_2"
|
||||
"keyword.directive" = { fg = "orange_2", modifiers = ["bold"] }
|
||||
"keyword.function" = "orange_2"
|
||||
"keyword.storage" = { fg = "orange_2", modifiers = ["bold"] }
|
||||
|
||||
"operator" = "purple_2"
|
||||
|
||||
"function" = "blue_2"
|
||||
"function.builtin" = "blue_2"
|
||||
"function.macro" = { fg = "blue_2", modifiers = ["bold"] }
|
||||
"function.special" = { fg = "blue_2", modifiers = ["bold"] }
|
||||
|
||||
"tag" = "teal_2"
|
||||
|
||||
"namespace" = "orange_2"
|
||||
|
||||
"markup" = "light_4"
|
||||
"markup.heading" = { fg = "teal_2", modifiers = ["bold"] }
|
||||
"markup.list" = { fg = "orange_2", modifiers = ["bold"] }
|
||||
"markup.bold" = { fg = "light_4", modifiers = ["bold"] }
|
||||
"markup.italic" = { fg = "light_4", modifiers = ["italic"] }
|
||||
"markup.link" = { fg = "blue_3", modifiers = ["underlined"] }
|
||||
"markup.quote" = { fg = "light_3", modifiers = ["italic"] }
|
||||
"diff.plus" = "teal_3"
|
||||
"diff.minus" = "red_1"
|
||||
"diff.delta" = "orange_3"
|
||||
"diff.delta.moved" = "orange_2"
|
||||
|
||||
"ui.background" = { fg = "light_4", bg = "libadwaita_dark" }
|
||||
"ui.background.separator" = { fg = "split_and_borders", bg = "libadwaita_dark" }
|
||||
"ui.cursor" = { fg = "libadwaita_dark", bg = "light_5" }
|
||||
"ui.cursor.insert" = { fg = "libadwaita_dark", bg = "light_5" }
|
||||
"ui.cursor.select" = { fg = "libadwaita_dark", bg = "light_5" }
|
||||
"ui.cursor.match" = { fg = "libadwaita_dark", bg = "blue_2" }
|
||||
"ui.cursor.primary" = { fg = "libadwaita_dark", bg = "light_7" }
|
||||
"ui.linenr" = "dark_2"
|
||||
"ui.linenr.selected" = { fg = "light_7", bg = "libadwaita_dark_alt", modifiers = [
|
||||
"bold",
|
||||
] }
|
||||
"ui.statusline" = { fg = "light_4", bg = "libadwaita_dark_alt" }
|
||||
"ui.statusline.inactive" = { fg = "light_4", bg = "libadwaita_dark_alt" }
|
||||
"ui.statusline.insert" = { fg = "light_4", bg = "teal_4" }
|
||||
"ui.statusline.select" = { fg = "light_4", bg = "blue_4" }
|
||||
"ui.popup" = { bg = "libadwaita_popup" }
|
||||
"ui.window" = "split_and_borders"
|
||||
"ui.help" = { bg = "libadwaita_dark_alt" }
|
||||
"ui.text" = "light_4"
|
||||
"ui.virtual" = "dark_1"
|
||||
"ui.menu" = { fg = "light_4", bg = "libadwaita_popup" }
|
||||
"ui.menu.selected" = { fg = "light_4", bg = "blue_5" }
|
||||
"ui.menu.scroll" = { fg = "light_7", bg = "dark_3" }
|
||||
"ui.selection" = { bg = "blue_7" }
|
||||
"ui.selection.primary" = { bg = "blue_7" }
|
||||
"ui.cursorline.primary" = { bg = "libadwaita_dark_alt" }
|
||||
|
||||
"warning" = "yellow_2"
|
||||
"error" = "red_4"
|
||||
"info" = "purple_2"
|
||||
"hint" = "blue_2"
|
||||
|
||||
"diagnostic.hint" = { fg = "blue_2", modifiers = ["dim"] }
|
||||
"diagnostic.info" = { fg = "purple_2", modifiers = ["dim"] }
|
||||
"diagnostic.error" = { fg = "red_4", modifiers = ["underlined"] }
|
||||
"diagnostic.warning" = { fg = "yellow_2", modifiers = ["underlined"] }
|
||||
|
||||
[palette]
|
||||
blue_1 = "#99C1F1"
|
||||
blue_2 = "#62A0EA"
|
||||
blue_3 = "#3584E4"
|
||||
blue_4 = "#1C71D8"
|
||||
blue_5 = "#1A5FB4"
|
||||
blue_6 = "#1B497E"
|
||||
blue_7 = "#193D66"
|
||||
brown_1 = "#CDAB8F"
|
||||
brown_2 = "#B5835A"
|
||||
brown_3 = "#986A44"
|
||||
brown_4 = "#865E3C"
|
||||
brown_5 = "#63452C"
|
||||
chameleon_3 = "#4E9A06"
|
||||
dark_1 = "#77767B"
|
||||
dark_2 = "#5E5C64"
|
||||
dark_3 = "#504E55"
|
||||
dark_4 = "#3D3846"
|
||||
dark_5 = "#241F31"
|
||||
dark_6 = "#000000"
|
||||
dark_7 = "#1c1c1c"
|
||||
green_1 = "#8FF0A4"
|
||||
green_2 = "#57E389"
|
||||
green_3 = "#33D17A"
|
||||
green_4 = "#2EC27E"
|
||||
green_5 = "#26A269"
|
||||
green_6 = "#1F7F56"
|
||||
green_7 = "#1C6849"
|
||||
libadwaita_dark = "#1D1D1D"
|
||||
libadwaita_dark_alt = "#303030"
|
||||
libadwaita_popup = "#282828"
|
||||
light_1 = "#FFFFFF"
|
||||
light_2 = "#FCFCFC"
|
||||
light_3 = "#F6F5F4"
|
||||
light_4 = "#DEDDDA"
|
||||
light_5 = "#C0BFBC"
|
||||
light_6 = "#B0AFAC"
|
||||
light_7 = "#9A9996"
|
||||
orange_1 = "#FFBE6F"
|
||||
orange_2 = "#FFA348"
|
||||
orange_3 = "#FF7800"
|
||||
orange_4 = "#E66100"
|
||||
orange_5 = "#C64600"
|
||||
purple_1 = "#DC8ADD"
|
||||
purple_2 = "#C061CB"
|
||||
purple_3 = "#9141AC"
|
||||
purple_4 = "#813D9C"
|
||||
purple_5 = "#613583"
|
||||
red_1 = "#F66151"
|
||||
red_2 = "#ED333B"
|
||||
red_3 = "#E01B24"
|
||||
red_4 = "#C01C28"
|
||||
red_5 = "#A51D2D"
|
||||
teal_1 = "#93DDC2"
|
||||
teal_2 = "#5BC8AF"
|
||||
teal_3 = "#33B2A4"
|
||||
teal_4 = "#26A1A2"
|
||||
teal_5 = "#218787"
|
||||
violet_2 = "#7D8AC7"
|
||||
violet_3 = "#6362C8"
|
||||
violet_4 = "#4E57BA"
|
||||
yellow_1 = "#F9F06B"
|
||||
yellow_2 = "#F8E45C"
|
||||
yellow_3 = "#F6D32D"
|
||||
yellow_4 = "#F5C211"
|
||||
yellow_5 = "#E5A50A"
|
||||
yellow_6 = "#D38B09"
|
||||
split_and_borders = "#4F4F4F"
|
@ -0,0 +1,114 @@
|
||||
# Author: Isotoxal <isotoxal@proton.me>
|
||||
|
||||
"attribute" = { fg = "blue" }
|
||||
"comment" = { fg = "comment", modifiers = ["italic"] }
|
||||
"constant" = { fg = "cyan" }
|
||||
"constant.builtin.boolean" = { fg = "cyan" }
|
||||
"constant.character" = { fg = "blue" }
|
||||
"constant.numeric.float" = { fg = "black-light" }
|
||||
"constant.builtin" = { fg = "blue" }
|
||||
"constant.numeric" = { fg = "yellow" }
|
||||
"constructor" = { fg = "blue" }
|
||||
"function" = { fg = "red" }
|
||||
"function.builtin" = { fg = "cyan-light" }
|
||||
"function.macro" = { fg = "green" }
|
||||
"function.method" = { fg = "blue-light" }
|
||||
"keyword" = { fg = "blue" }
|
||||
"keyword.function" = { fg = "blue" }
|
||||
"keyword.operator" = { fg = "blue-light" }
|
||||
"keyword.control.conditional" = { fg = "red" }
|
||||
"keyword.control.import" = { fg = "red-light" }
|
||||
"keyword.control.return" = { fg = "blue" }
|
||||
"keyword.control.repeat" = { fg = "yellow-light" }
|
||||
"keyword.control.exception" = { fg = "black-light" }
|
||||
"label" = { fg = "blue" }
|
||||
"namespace" = { fg = "red-light" }
|
||||
"operator" = { fg = "white" }
|
||||
#"parameter.reference" = { fg = "red-light" }
|
||||
#"property" = { fg = "red" }
|
||||
"punctuation.bracket" = { fg = "white" }
|
||||
"punctuation.delimiter" = { fg = "white" }
|
||||
"punctuation.special" = { fg = "white" }
|
||||
"string" = { fg = "green" }
|
||||
"string.escape" = { fg = "blue" }
|
||||
"string.regex" = { fg = "green" }
|
||||
"string.special" = { fg = "blue" }
|
||||
"string.special.symbol" = { fg = "red" }
|
||||
"tag" = { fg = "blue" }
|
||||
"type" = { fg = "yellow" }
|
||||
"type.builtin" = { fg = "yellow" }
|
||||
"variable" = { fg = "white" }
|
||||
"variable.builtin" = { fg = "blue" }
|
||||
"variable.parameter" = { fg = "red" }
|
||||
"variable.other.member" = { fg = "red" }
|
||||
|
||||
"diff.plus" = { fg = "blue" }
|
||||
"diff.delta" = { fg = "magenta" }
|
||||
"diff.minus" = { fg = "red" }
|
||||
|
||||
"ui.background" = { fg = "foreground", bg = "background" }
|
||||
"ui.cursor" = { modifiers = ["reversed"] }
|
||||
"ui.cursorline.primary" = { bg = "cursorline" }
|
||||
"ui.help" = { fg = "foreground", bg = "contrast" }
|
||||
"ui.linenr" = { fg = "comment" }
|
||||
"ui.linenr.selected" = { fg = "foreground" }
|
||||
"ui.menu" = { fg = "foreground", bg = "contrast" }
|
||||
"ui.menu.selected" = { bg = "black" }
|
||||
"ui.popup" = { fg = "foreground", bg = "contrast" }
|
||||
"ui.selection" = { bg = "black" }
|
||||
"ui.selection.primary" = { bg = "black" }
|
||||
"ui.statusline" = { fg = "foreground", bg = "background" }
|
||||
"ui.statusline.inactive" = { fg = "foreground", bg = "background" }
|
||||
"ui.statusline.normal" = { fg = "white", bg = "background" }
|
||||
"ui.statusline.insert" = { fg = "blue", bg = "background" }
|
||||
"ui.statusline.select" = { fg = "cyan", bg = "magenta" }
|
||||
"ui.text" = { fg = "foreground" }
|
||||
"ui.text.focus" = { fg = "blue" }
|
||||
"ui.virtual.ruler" = { bg = "cursorline" }
|
||||
"ui.virtual.whitespace" = { fg = "comment" }
|
||||
"ui.virtual.wrap" = { fg = "comment" }
|
||||
"ui.virtual.indent-guide" = { fg = "comment" }
|
||||
"ui.window" = { fg = "black" }
|
||||
|
||||
"error" = { fg = "red" }
|
||||
"hint" = { fg = "green" }
|
||||
"warning" = { fg = "yellow" }
|
||||
"info" = { fg = "blue" }
|
||||
"diagnostic.error" = { underline = { style = "curl", color = "red" } }
|
||||
"diagnostic.warning" = { underline = { style = "curl", color = "yellow" } }
|
||||
"diagnostic.info" = { underline = { style = "curl", color = "blue" } }
|
||||
"diagnostic.hint" = { underline = { style = "curl", color = "green" } }
|
||||
"special" = { fg = "red-light" }
|
||||
|
||||
"markup.heading" = { fg = "blue", modifiers = ["bold"] }
|
||||
"markup.list" = { fg = "cyan" }
|
||||
"markup.bold" = { fg = "magenta", modifiers = ["bold"] }
|
||||
"markup.italic" = { fg = "yellow", modifiers = ["italic"] }
|
||||
"markup.strikethrough" = { modifiers = ["crossed_out"] }
|
||||
"markup.link.url" = { fg = "green" }
|
||||
"markup.link.text" = { fg = "black-light" }
|
||||
"markup.quote" = { fg = "yellow", modifiers = ["italic"] }
|
||||
"markup.raw" = { fg = "cyan" }
|
||||
|
||||
[palette]
|
||||
black = "#232a2d"
|
||||
red = "#e57474"
|
||||
green = "#8ccf7e"
|
||||
yellow = "#e5c76b"
|
||||
blue = "#67b0e8"
|
||||
magenta = "#c47fd5"
|
||||
cyan = "#6cbfbf"
|
||||
white = "#b3b9b8"
|
||||
black-light = "#2d3437"
|
||||
red-light = "#ef7e7e"
|
||||
green-light = "#96d988"
|
||||
yellow-light = "#f4d67a"
|
||||
blue-light = "#71baf2"
|
||||
magenta-light = "#ce89df"
|
||||
cyan-light = "#67cbe7"
|
||||
white-light = "#bdc3c2"
|
||||
comment = "#404749"
|
||||
contrast = "#161d1f"
|
||||
background = "#141b1e"
|
||||
foreground = "#dadada"
|
||||
cursorline = "#2c3333"
|
@ -1,129 +1,184 @@
|
||||
# One Light
|
||||
# Author : erasin<erasinoo@gmail.com>
|
||||
|
||||
"attribute" = { fg = "yellow" }
|
||||
"comment" = { fg = "gray", modifiers = ["italic"] }
|
||||
"constructor" = { fg = "brown" }
|
||||
"label" = { fg = "cyan" }
|
||||
"operator" = { fg = "red" }
|
||||
"tag" = { fg = "cyan" }
|
||||
"namespace" = { fg = "blue" }
|
||||
"special" = { fg = "deep-purple" }
|
||||
"property" = { fg = "purple" }
|
||||
"module" = { fg = "cyan" }
|
||||
|
||||
"constant" = { fg = "cyan" }
|
||||
"type" = { fg = "gold" }
|
||||
"type.builtin" = { fg = "light-blue" }
|
||||
"type.enum" = { fg = "cyan" }
|
||||
"type.enum.variant" = { fg = "cyan" }
|
||||
|
||||
"constant" = { fg = "cyan", modifiers = ["bold"] }
|
||||
"constant.builtin" = { fg = "deep-purple" }
|
||||
"constant.builtin.boolean" = { fg = "deep-purple" }
|
||||
"constant.character" = { fg = "green" }
|
||||
"constant.character.escape" = { fg = "brown" }
|
||||
"constant.numeric" = { fg = "gold" }
|
||||
"constant.builtin" = { fg = "gold" }
|
||||
"constant.character.escape" = { fg = "gold" }
|
||||
"constant.numeric.integer" = { fg = "gold" }
|
||||
"constant.numeric.float" = { fg = "gold" }
|
||||
|
||||
"constructor" = { fg = "yellow" }
|
||||
"string" = { fg = "green" }
|
||||
"string.regexp" = { fg = "purple" }
|
||||
"string.special" = { fg = "green" }
|
||||
"string.special.path" = { fg = "blue" }
|
||||
"string.special.url" = { fg = "light-blue" }
|
||||
"string.special.symbol" = { fg = "pink" }
|
||||
|
||||
"comment" = { fg = "grey", modifiers = ["italic"] }
|
||||
"comment.line" = { fg = "grey", modifiers = ["italic"] }
|
||||
"comment.block" = { fg = "grey", modifiers = ["italic"] }
|
||||
"comment.block.documentation" = { fg = "grey", modifiers = ["italic"] }
|
||||
|
||||
# "variable" = { fg = "black" }
|
||||
"variable.builtin" = { fg = "light-blue" }
|
||||
"variable.parameter" = { fg = "red" }
|
||||
"variable.other" = { fg = "pink" }
|
||||
"variable.other.member" = { fg = "pink" }
|
||||
|
||||
"function" = { fg = "blue" }
|
||||
"function.builtin" = { fg = "cyan" }
|
||||
"function.macro" = { fg = "red" }
|
||||
"punctuation" = { fg = "black" }
|
||||
"punctuation.delimiter" = { fg = "purple" }
|
||||
"punctuation.bracket" = { fg = "brown" }
|
||||
"punctuation.special" = { fg = "brown" }
|
||||
|
||||
"keyword" = { fg = "purple" }
|
||||
"keyword.function" = { fg = "purple" }
|
||||
"keyword.control" = { fg = "purple" }
|
||||
"keyword.control.import" = { fg = "purple" }
|
||||
"keyword.control.conditional" = { fg = "red", modifiers = ["bold"] }
|
||||
"keyword.control.repeat" = { fg = "pink", modifiers = ["bold"] }
|
||||
"keyword.control.import" = { fg = "red" }
|
||||
"keyword.control.return" = { fg = "deep-purple", modifiers = ["bold"] }
|
||||
"keyword.control.exception" = { fg = "purple" }
|
||||
"keyword.operator" = { fg = "red" }
|
||||
"keyword.directive" = { fg = "purple" }
|
||||
"keyword.operator" = { fg = "purple" }
|
||||
"keyword.function" = { fg = "purple" }
|
||||
"keyword.storage" = { fg = "purple" }
|
||||
"keyword.storage.type" = { fg = "purple" }
|
||||
"keyword.storage.modifier" = { fg = "purple", modifiers = ["bold"] }
|
||||
|
||||
"tag" = "cyan"
|
||||
"label" = { fg = "cyan" }
|
||||
"namespace" = { fg = "red" }
|
||||
"operator" = { fg = "red" }
|
||||
"special" = { fg = "purple" }
|
||||
"string" = { fg = "green" }
|
||||
"module" = { fg = "cyan" }
|
||||
|
||||
"type" = { fg = "yellow" }
|
||||
"type.builtin" = { fg = "purple" }
|
||||
|
||||
"punctuation" = { fg = "gray" }
|
||||
"punctuation.delimiter" = { fg = "black" }
|
||||
"punctuation.bracket" = { fg = "gray" }
|
||||
|
||||
"variable" = { fg = "black" }
|
||||
"variable.builtin" = { fg = "light-blue" }
|
||||
"variable.parameter" = { fg = "red" }
|
||||
"variable.other.member" = { fg = "red" }
|
||||
"function" = { fg = "blue" }
|
||||
"function.builtin" = { fg = "cyan" }
|
||||
"function.method" = { fg = "light-blue" }
|
||||
"function.macro" = { fg = "pink", modifiers = ["bold"] }
|
||||
"function.special" = { fg = "cyan" }
|
||||
|
||||
"markup.heading" = { fg = "red" }
|
||||
"markup.raw" = { fg = "gray" }
|
||||
"markup.raw.inline" = { fg = "green", bg = "grey-200" }
|
||||
"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
|
||||
"markup.italic" = { fg = "purple", modifiers = ["italic"] }
|
||||
"markup.strikethrough" = { modifiers = ["crossed_out"] }
|
||||
"markup.list" = { fg = "light-blue" }
|
||||
"markup.quote" = { fg = "gray" }
|
||||
"markup.link.url" = { fg = "cyan", modifiers = ["underlined"] }
|
||||
"markup.link.text" = { fg = "light-blue" }
|
||||
"markup.heading.marker" = { fg = "red" }
|
||||
"markup.heading.1" = { fg = "red", modifiers = ["bold"] }
|
||||
"markup.heading.2" = { fg = "gold", modifiers = ["bold"] }
|
||||
"markup.heading.2" = { fg = "gold", modifiers = [
|
||||
"bold",
|
||||
], underline = { style = "line" } }
|
||||
"markup.heading.3" = { fg = "yellow", modifiers = ["bold"] }
|
||||
"markup.heading.4" = { fg = "green", modifiers = ["bold"] }
|
||||
"markup.heading.5" = { fg = "blue", modifiers = ["bold"] }
|
||||
"markup.heading.6" = { fg = "purple", modifiers = ["bold"] }
|
||||
"markup.list" = { fg = "light-blue" }
|
||||
"markup.list.unnumbered" = { fg = "light-blue" }
|
||||
"markup.list.numbered" = { fg = "light-blue" }
|
||||
"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
|
||||
"markup.italic" = { fg = "purple", modifiers = ["italic"] }
|
||||
"markup.link" = { fg = "light-blue" }
|
||||
"markup.link.url" = { fg = "cyan", modifiers = ["underlined"] }
|
||||
"markup.link.text" = { fg = "light-blue" }
|
||||
"markup.quote" = { fg = "grey" }
|
||||
"markup.raw" = { fg = "brown" }
|
||||
"markup.raw.inline" = { fg = "green" }
|
||||
"markup.raw.block" = { fg = "grey" }
|
||||
|
||||
"diff.plus" = "green"
|
||||
"diff.delta" = "gold"
|
||||
"diff.minus" = "red"
|
||||
|
||||
"diagnostic.info".underline = { color = "blue", style = "curl" }
|
||||
"diagnostic.hint".underline = { color = "green", style = "curl" }
|
||||
"diagnostic.warning".underline = { color = "yellow", style = "curl" }
|
||||
"diagnostic.error".underline = { color = "red", style = "curl" }
|
||||
|
||||
"info" = { fg = "blue", modifiers = ["bold"] }
|
||||
"hint" = { fg = "green", modifiers = ["bold"] }
|
||||
"warning" = { fg = "yellow", modifiers = ["bold"] }
|
||||
"error" = { fg = "red", modifiers = ["bold"] }
|
||||
"diff" = { fg = "red" }
|
||||
"diff.plus" = { fg = "green" }
|
||||
"diff.minus" = { fg = "red" }
|
||||
"diff.delta" = { fg = "cyan" }
|
||||
"diff.delta.moved" = { fg = "cyan" }
|
||||
|
||||
"ui.background" = { bg = "white" }
|
||||
"ui.background.separator" = { bg = "white" }
|
||||
|
||||
"ui.cursor" = { fg = "white", bg = "gray" }
|
||||
"ui.cursor" = { fg = "white", bg = "grey" }
|
||||
"ui.cursor.normal" = { fg = "white", bg = "grey" }
|
||||
"ui.cursor.insert" = { fg = "white", bg = "grey" }
|
||||
"ui.cursor.select" = { fg = "white", bg = "grey" }
|
||||
"ui.cursor.match" = { bg = "light-white", modifiers = ["bold"] }
|
||||
"ui.cursor.primary" = { fg = "white", bg = "black" }
|
||||
"ui.cursor.match" = { bg = "light-gray" }
|
||||
"ui.cursor.primary.normal" = { fg = "white", bg = "black" }
|
||||
"ui.cursor.primary.insert" = { fg = "red", bg = "black" }
|
||||
"ui.cursor.primary.select" = { fg = "white", bg = "black" }
|
||||
|
||||
"ui.cursorline.primary" = { fg = "white", bg = "grey-100" }
|
||||
# "ui.cursorline.secondary" = { fg = "white", bg = "grey-200" }
|
||||
|
||||
"ui.highlight" = { bg = "light-white" }
|
||||
|
||||
"ui.selection" = { bg = "light-white", modifiers = ["dim"] }
|
||||
"ui.selection.primary" = { bg = "light-white" }
|
||||
|
||||
"ui.virtual" = { fg = "light-white" }
|
||||
"ui.virtual.indent-guide" = { fg = "grey-500" }
|
||||
"ui.virtual.ruler" = { bg = "light-white" }
|
||||
"ui.virtual.whitespace" = { fg = "light-white" }
|
||||
"ui.gutter" = { fg = "grey-500" }
|
||||
"ui.gutter.selected" = { fg = "black" }
|
||||
|
||||
"ui.linenr" = { fg = "grey-500" }
|
||||
"ui.linenr.selected" = { fg = "black", modifiers = ["dim"] }
|
||||
"ui.linenr.selected" = { fg = "black", modifiers = ["bold"] }
|
||||
|
||||
"ui.statusline" = { fg = "black", bg = "light-white" }
|
||||
"ui.statusline.inactive" = { fg = "gray", bg = "light-white" }
|
||||
"ui.statusline.inactive" = { fg = "grey", bg = "grey-200" }
|
||||
"ui.statusline.normal" = { fg = "light-white", bg = "light-blue" }
|
||||
"ui.statusline.insert" = { fg = "light-white", bg = "green" }
|
||||
"ui.statusline.select" = { fg = "light-white", bg = "purple" }
|
||||
|
||||
"ui.popup" = { fg = "black", bg = "grey-200" }
|
||||
"ui.popup.info" = { fg = "black", bg = "grey-200" }
|
||||
"ui.window" = { fg = "grey-500", bg = "grey-100" }
|
||||
"ui.help" = { fg = "black", bg = "grey-200" }
|
||||
|
||||
"ui.text" = { fg = "black" }
|
||||
"ui.text.focus" = { fg = "red", bg = "light-white", modifiers = ["bold"] }
|
||||
"ui.text.inactive" = { fg = "grey" }
|
||||
"ui.text.info" = { fg = "black" }
|
||||
|
||||
"ui.virtual" = { fg = "light-white" }
|
||||
"ui.virtual.ruler" = { bg = "light-white" }
|
||||
"ui.virtual.wrap" = { bg = "light-white" }
|
||||
"ui.virtual.whitespace" = { fg = "light-white" }
|
||||
"ui.virtual.indent-guide" = { fg = "grey-500" }
|
||||
"ui.virtual.inlay-hint" = { fg = "grey" }
|
||||
|
||||
"ui.help" = { fg = "black", bg = "grey-200" }
|
||||
"ui.popup" = { fg = "black", bg = "grey-200" }
|
||||
"ui.window" = { fg = "black", bg = "light-white" }
|
||||
"ui.menu" = { fg = "black", bg = "light-white" }
|
||||
"ui.menu.selected" = { fg = "white", bg = "light-blue" }
|
||||
"ui.menu.scroll" = { fg = "light-blue", bg = "white" }
|
||||
|
||||
"ui.selection" = { bg = "light-white", modifiers = ["dim"] }
|
||||
"ui.selection.primary" = { bg = "light-white" }
|
||||
|
||||
"ui.cursorline.primary" = { fg = "white", bg = "grey-100" }
|
||||
"ui.cursorline.secondary" = { fg = "white", bg = "grey-200" }
|
||||
|
||||
"ui.cursorcolumn.primary" = { fg = "white", bg = "grey-100" }
|
||||
"ui.cursorcolumn.secondary" = { fg = "white", bg = "grey-200" }
|
||||
|
||||
"ui.highlight" = { bg = "light-white" }
|
||||
|
||||
"diagnostic.info" = { underline = { color = "blue", style = "dotted" } }
|
||||
"diagnostic.hint" = { underline = { color = "green", style = "dashed" } }
|
||||
"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } }
|
||||
"diagnostic.error" = { underline = { color = "red", style = "curl" } }
|
||||
|
||||
"info" = { fg = "blue", modifiers = ["bold"] }
|
||||
"hint" = { fg = "green", modifiers = ["bold"] }
|
||||
"warning" = { fg = "yellow", modifiers = ["bold"] }
|
||||
"error" = { fg = "red", modifiers = ["bold"] }
|
||||
|
||||
[palette]
|
||||
white = "#FAFAFA"
|
||||
yellow = "#A06600"
|
||||
yellow = "#FF6F00"
|
||||
gold = "#D35400"
|
||||
brown = "#4E342E"
|
||||
blue = "#0061FF"
|
||||
light-blue = "#1877F2"
|
||||
red = "#DC003F"
|
||||
light-blue = "#0091EA"
|
||||
red = "#D50000"
|
||||
pink = "#C2185B"
|
||||
purple = "#B500A9"
|
||||
deep-purple = "#651FFF"
|
||||
green = "#24A443"
|
||||
gold = "#D35400"
|
||||
cyan = "#0086C1"
|
||||
black = "#282C34"
|
||||
light-white = "#E3E3E3"
|
||||
gray = "#5C6370"
|
||||
grey = "#5C6370"
|
||||
grey-100 = "#F3F3F3"
|
||||
grey-200 = "#EDEDED"
|
||||
grey-500 = "#9E9E9E"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue