├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── docs ├── README.md ├── completions │ └── kak-tree-sitter.nu ├── design │ ├── hooks.md │ ├── request.md │ ├── sessions.md │ └── v0.0.1.md └── man │ ├── JSX.md │ ├── README.md │ ├── commands.md │ ├── completions.md │ ├── configuration.md │ ├── faq.md │ ├── features.md │ ├── getting-started.md │ ├── highlighting.md │ ├── how-to-install.md │ ├── ktsctl.md │ ├── text-objects.md │ ├── tweaking.md │ └── usage.md ├── kak-tree-sitter-config ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── default-config.toml └── src │ ├── error.rs │ ├── lib.rs │ └── source.rs ├── kak-tree-sitter ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── build.rs ├── rc │ ├── static.kak │ └── text-objects.kak └── src │ ├── cli.rs │ ├── client.rs │ ├── error.rs │ ├── kakoune.rs │ ├── kakoune │ ├── buffer.rs │ ├── rc.rs │ ├── selection.rs │ ├── session.rs │ └── text_objects.rs │ ├── logging.rs │ ├── main.rs │ ├── protocol.rs │ ├── protocol │ ├── request.rs │ └── response.rs │ ├── server.rs │ ├── server │ ├── fifo.rs │ ├── handler.rs │ ├── resources.rs │ └── tokens.rs │ ├── tree_sitter.rs │ └── tree_sitter │ ├── highlighting.rs │ ├── languages.rs │ ├── nav.rs │ ├── queries.rs │ └── state.rs ├── ktsctl ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src │ ├── cli.rs │ ├── commands.rs │ ├── commands │ ├── manage.rs │ ├── query.rs │ └── remove.rs │ ├── error.rs │ ├── fs.rs │ ├── git.rs │ ├── main.rs │ ├── process.rs │ ├── resources.rs │ ├── ui.rs │ └── ui │ ├── report.rs │ ├── section.rs │ ├── source.rs │ ├── status_icon.rs │ └── table.rs ├── runtime ├── README.md └── queries │ ├── astro │ ├── LICENSE │ ├── README.md │ ├── highlights.scm │ └── injections.scm │ ├── cpp │ ├── LICENSE │ ├── README.md │ ├── highlights.scm │ ├── indents.scm │ ├── injections.scm │ └── textobjects.scm │ ├── javascript │ ├── LICENSE │ ├── README.md │ ├── highlights.scm │ ├── indents.scm │ ├── injections.scm │ ├── locals.scm │ ├── tags.scm │ └── textobjects.scm │ ├── jsx │ ├── LICENSE │ ├── README.md │ ├── highlights.scm │ ├── indents.scm │ ├── injections.scm │ ├── locals.scm │ ├── tags.scm │ └── textobjects.scm │ ├── markdown │ ├── LICENSE │ ├── README.md │ ├── highlights.scm │ └── injections.scm │ ├── tsx │ ├── LICENSE │ ├── README.md │ ├── highlights.scm │ ├── indents.scm │ ├── injections.scm │ ├── locals.scm │ ├── tags.scm │ └── textobjects.scm │ └── typescript │ ├── LICENSE │ ├── README.md │ ├── highlights.scm │ ├── indents.scm │ ├── injections.scm │ ├── locals.scm │ ├── tags.scm │ └── textobjects.scm └── rustfmt.toml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | versioning-strategy: auto 5 | directory: "/." 6 | schedule: 7 | interval: weekly 8 | time: "04:00" 9 | open-pull-requests-limit: 10 10 | target-branch: master 11 | reviewers: 12 | - hadronized 13 | assignees: 14 | - hadronized 15 | labels: 16 | - dependency-update 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [pull_request] 3 | 4 | jobs: 5 | build-linux: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - name: Clippy 10 | run: cargo clippy 11 | - name: Build and test 12 | run: cargo test 13 | 14 | build-macosx: 15 | runs-on: macOS-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Clippy 19 | run: cargo clippy 20 | - name: Build 21 | run: cargo test 22 | 23 | quality: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: rustfmt 28 | run: cargo fmt --check 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Everyone is welcome to contribute. There is no small contributions. Please take 4 | the time to read this document before starting. We are using [git] on [GitHub]. 5 | Please ensure to get familiarized with both before starting to contribute. 6 | 7 | ## Contributing language support 8 | 9 | **An important guideline here**: [Helix](https://helix-editor.com/) is a well 10 | appreciated editor and their queries are pretty excellent. You are highly 11 | suggested to reuse their work and point the `url` of the configuration you add 12 | to their repository. For `queries`, the `url` is most likely to always be 13 | `https://github.com/helix-editor/helix`, and the `path` something like 14 | `runtime/queries/`. **Please fill in the `pin` option for both `queries` 15 | and `grammar`**. 16 | 17 | For the grammar to use, you are advised to look in 18 | [this Helix’ languages.toml](https://github.com/helix-editor/helix/blob/master/languages.toml) 19 | file. It even has the `rev` (i.e. what we call `pin`) to use. 20 | 21 | Sometimes, the queries from Helix are not enough, because it contains Helix’ 22 | specificities (like capture nodes for injections that is not exactly what we 23 | use, or `; inherits` annotations). In such cases, you need to check the queries 24 | in [our runtime/queries](./runtime/queries) directory. Refer to already existing 25 | queries for the process **but you must credit the source, license terms and copy 26 | the `LICENSE` file in the language directory**. FOSS should be respected; let us 27 | be an example. 28 | 29 | ## Commit hygiene 30 | 31 | Please refrain from creating gigantic commits. I reserve the right to refuse 32 | your patch if it’s not atomic enough: I engage my spare-time to review and 33 | understand your code so **please** keep that in mind. 34 | 35 | There is no limit on the number of commits per PR, but keep in mind that 36 | individual commits should still remain small enough to be easily reviewable. Try 37 | to scope a PR down to a single issue, or even subpart of a issue if you think 38 | it makes sense. Remember that PRs are often reviewed commits by commits, so 39 | ensure a certain coherenc between what to put and what not to put in a commit. 40 | 41 | Also, remember to include the issue number at the end of your commit message 42 | with a leading dash — e.g. `#123` – and to write concise yet acute commit 43 | messages. Those are used for writing changelog, so please keep them short. 44 | 45 | Finally, **merging `master` into your branch is not appreciated**, and will end 46 | up with your patch refused. If you want to “synchronize” your work with the 47 | recent changes, please use `git fetch origin && git rebase origin/master` in 48 | your branch. 49 | 50 | ## Sign your work 51 | 52 | GPG signatures are used to sign our work. The value of signing a piece of code 53 | doesn’t imply _you_ write it, but it implies you _validated that code_, and in 54 | the end, we don’t really care whether you wrote the code or whether you 55 | generated with a fancy A.I. generator. It’s the code you bring and it’s the code 56 | you sign. 57 | 58 | If you plan on contributing more than just one-shot contributions, feel free 59 | to open a PR to modify the [MAINTAINERS.md] file by providing your name, email 60 | address and PGP fingerprint. 61 | 62 | [git]: https://git-scm.com/ 63 | [GitHub]: https://github.com/hadronized/kak-tree-sitter 64 | [keys.openpgp.org]: https://keys.openpgp.org/ 65 | [MAINTAINERS.md]: MAINTAINERS.md 66 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "kak-tree-sitter", 6 | "kak-tree-sitter-config", 7 | "ktsctl", 8 | ] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023,2024 Dimitri Sabadie 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Dimitri Sabadie nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | Dimitri Sabadie 2 | PGP fingerprint: 6C99 1AA0 6D7A 7C28 5737 99CE A943 86A8 A625 2ECB 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archive notice 2 | 3 | [The project moved to SourceHut.](https://sr.ht/~hadronized/kak-tree-sitter/) 4 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Project documents 2 | 3 | This document is the entry-point for all documents related to the project. 4 | 5 | - [A user manual](man), explaing how to use every parts of the project. 6 | - [A design section](design), explaining different part of the project. 7 | -------------------------------------------------------------------------------- /docs/completions/kak-tree-sitter.nu: -------------------------------------------------------------------------------- 1 | # Completions for kak-tree-sitter and ktsctl. 2 | 3 | # List of all known languages by KTS. 4 | export def "nu-complete ktsctl-available-langs" [] { 5 | ^ktsctl query -a | str trim | lines | skip 2 | split column '|' lang | get lang 6 | } 7 | 8 | # The Kakoune tree-sitter client and server 9 | export extern kak-tree-sitter [ 10 | --kakoune (-k) # Whether we start from Kakoune 11 | --init: string # Initialize the current session by injecting some rc 12 | --server (-s) # Start the server, if not already started 13 | --daemonize (-d) # Try to daemonize, if not already done 14 | --client (-c): string # Kakoune client to connect with, if any 15 | --request (-r): string # JSON-serialized request 16 | --with-highlighting # Insert Kakoune code related to highlighting 17 | --with-text-objects # Insert Kakoune code related to text-objects 18 | --verbose (-v) # Verbosity (can be accumulated) 19 | --help (-h) # Print help 20 | --version (-V) # Print version 21 | ] 22 | 23 | # The Kakoune tree-sitter controller 24 | export extern ktsctl [ 25 | --verbose (-v) # Verbosity (can be accumulated) 26 | --help (-h) # Print help 27 | --version (-V) # Print version 28 | ] 29 | 30 | # Fetch resources 31 | export extern "ktsctl fetch" [ 32 | lang?: string@"nu-complete ktsctl-available-langs" # Language to fetch 33 | --all (-a) # Fetch all languages 34 | --help (-h) # Print help 35 | ] 36 | 37 | # Compile resources 38 | export extern "ktsctl compile" [ 39 | lang: string@"nu-complete ktsctl-available-langs" # Language to compile 40 | --all (-a) # Compile all languages 41 | --help (-h) # Print help 42 | ] 43 | 44 | # Install resources 45 | export extern "ktsctl install" [ 46 | lang?: string@"nu-complete ktsctl-available-langs" # Language to install 47 | --all (-a) # Install all languages 48 | --help (-h) # Print help 49 | ] 50 | 51 | # Synchronize resources 52 | export extern "ktsctl sync" [ 53 | lang?: string@"nu-complete ktsctl-available-langs" # Language to synchronize 54 | --all (-a) # Synchronize all languages 55 | --help (-h) # Print help 56 | ] 57 | 58 | # Query resources 59 | export extern "ktsctl query" [ 60 | lang?: string@"nu-complete ktsctl-available-langs" # Language to query 61 | --all (-a) # Query all languages 62 | --help (-h) # Print help 63 | ] 64 | 65 | # Remove resources (alias: rm) 66 | export extern "ktsctl remove" [ 67 | lang: string@"nu-complete ktsctl-available-langs" # Language to query 68 | --grammar (-g) # Remove grammar 69 | --queries (-q) # Remove queries 70 | --prune (-p) # Prune resources 71 | --help (-h) # Print help 72 | ] 73 | 74 | # Print help 75 | export extern "ktsctl help" [] 76 | -------------------------------------------------------------------------------- /docs/design/hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks 2 | -------------------------------------------------------------------------------- /docs/design/request.md: -------------------------------------------------------------------------------- 1 | # Tree-sitter requests 2 | 3 | The server supports many requests that clients can send. 4 | 5 | ## Session setup 6 | 7 | When a session starts, it should call something like `kak-tree-sitter -dks …`. 8 | The `-k` (`--kakoune`) flag indicates that the server is started from within 9 | Kakoune. This flag is important for things like initial logging and injecting 10 | commands into Kakoune, which is done via the `--init ` flag. When this 11 | flag is passed, the server writes commands to _stdout_, which is then 12 | interpreted by Kakoune. **Those commands are mandatory for the integration to 13 | work correctly.** 14 | 15 | > _Why having two flags; i.e. `--kakoune` and `--init`?_ 16 | > 17 | > `--kakoune` is used to state that we start from Kakoune. It’s used when 18 | > initiating a session — typicall from inside your `kakrc` — but it is also used 19 | > whenever a command is issued to the server. That allows the command to log 20 | > directly into Kakoune, and have more feedback. 21 | > 22 | > `--init` is just used once when initiating a session. 23 | 24 | ## Initial request and response 25 | 26 | Because the server is started with `--init $kts_session`, an initial request 27 | is emitted once the server is started: `session_begin`. Upon receiving that 28 | request, the KTS server knows that the session exists and that it needs to be 29 | configured. Configuring a session implies: 30 | 31 | - Tracking the session. This is needed so that when the session quits, we can 32 | perform the required checks to know whether we should shutdown the server. 33 | - Enable support for enabled languages, which sets some hooks when a buffer is 34 | open for such languages. 35 | 36 | ## Session exit 37 | 38 | When a session quits, it emits the `session_end` request so that the KTS server 39 | can cleanup the resources (mostly FIFOs) of the session and check whether it 40 | should quit. 41 | 42 | ## Configuration reload 43 | 44 | The `reload` request can be used at anytime to ask the KTS server to reload 45 | its configuration. Upon reloading, languages, grammars and queries are also 46 | replaced with the fresh configuration. 47 | 48 | If reloading fails, no change is applied to the running KTS server. 49 | 50 | ## Explicit shutdown 51 | 52 | It’s possible to explicitly ask the server to shutdown with the `shutdown` 53 | request. 54 | 55 | ## Buffer metadata / setup 56 | 57 | Once a session is fully initiated with KTS, it has some hooks for configured 58 | languages. Opening a buffer for such language will trigger such hooks, which 59 | will eventually send the `buffer_metadata` request. 60 | 61 | The `buffer_metadata` request passes some information about the buffer required 62 | for tree-sitter parsing. Currently, only the language the buffer is written in 63 | is passed, read from the `tree_sitter_lang` `window` option. 64 | 65 | It is possible to make a `buffer_metadata` request several time for the same 66 | buffer, as the request should be idempotent. However, if the language has 67 | changed, the buffer will be reset and setup again. 68 | 69 | KTS will send responses to `buffer_metadata` that will: 70 | 71 | - Set the FIFO path to stream the buffer content to. 72 | - Set the _buffer sentinel_ for this buffer (see the buffer update section). 73 | 74 | ## Buffer update 75 | 76 | Buffer update is a central concern in KTS. Doing it right is hard, because 77 | Kakoune doesn’t provide a way to easily share buffers. Once a buffer has 78 | changed, we can make KTS know about it by: 79 | 80 | - Writing the content to a given path. This is not ideal, because it induces a 81 | lot of I/O, especially on systems where _tmpfs_ is not supported (e.g. macOS). 82 | - Writing the content to a FIFO. FIFO are special files that act as 83 | _rendez-vous_ gates between two ends (the producer and consumer, the producer 84 | being Kakoune and the consumer being the KTS server here). In terms of 85 | performance, this solution is much better than writing to regular files. 86 | - Using shared memory. This solution would require Kakoune to expose a shared 87 | read-only memory segment of its internal representation of a buffer, which 88 | would be the best solution, but probably hard to work with even if it was 89 | available. 90 | 91 | FIFOs are currently the best way for us to minimize the overhead of having to 92 | stream buffers between two processes (Kakoune and KTS). Editors with a non-UNIX 93 | design typically push features into the editor directly, which has the benefit 94 | of putting all the features near the same memory region, but with Kakoune we 95 | cannot do that, since the editor itself doesn’t get new features in; we have to 96 | build them _externally_. 97 | 98 | Once a buffer needs to stream its updated content to the KTS server, it does two 99 | operations: 100 | 101 | - `write` to the FIFO. The `write` Kakoune command writes the content of the 102 | buffer to the specified file, so here, we write the content into the FIFO. 103 | - Once the content is written to the FIFO (which is open in non-blocking on the 104 | KTS side), the _buffer sentinel_ is written to the FIFO. 105 | 106 | The _buffer sentinel_ is a special string (UUID v4) set when the buffer is 107 | setup with tree-sitter, and marks the end of the buffer. This is a protocol 108 | detail that is required to know when the buffer is fully streamed to the FIFO. 109 | 110 | > It is possible that buffer updates trigger more asynchronous responses from 111 | > the KTS server; for instance if it was started with `--with-highlighting`. 112 | 113 | ## Buffer close 114 | 115 | `buffer_close` can be passed when a buffer is closed, which cleans resources 116 | from KTS for this buffer. 117 | 118 | ## TODO Text objects 119 | 120 | ## TODO Nav 121 | -------------------------------------------------------------------------------- /docs/design/sessions.md: -------------------------------------------------------------------------------- 1 | # Session support 2 | 3 | ## One process for all sessions 4 | 5 | KTS works on a per-session principle. A single server replies to requests from 6 | all the Kakoune sessions; effectively, a single server process handles all the 7 | Kakoune sessions on your machine. 8 | 9 | ## Buffer isolation 10 | 11 | If you open the same file in two different sessions, they will be handled as 12 | two different buffers by KTS, since they truly are two different buffers in 13 | Kakoune too. KTS does not make any assumption about the buffers; they can be 14 | associated with a filesystem file, or not. 15 | 16 | ## Session and KTS lifecycle 17 | 18 | When started from within Kakoune (i.e. `-k --kakoune`), sessions play an 19 | important role in the lifecycle of KTS. Indeed, a first initial session is 20 | needed to start KTS from Kakoune (passed with `--session $kak_session`). If a 21 | new session starts, it will not start a KTS server but instead will join the 22 | already existing one. 23 | 24 | Once a session exits, the KTS server takes it into account and keeps track of 25 | active session. When the number of session reaches zero, the server exits. 26 | 27 | > This is true even if the last session is not the first initial one. 28 | 29 | ## Recovering session data 30 | 31 | If KTS abruptly quits or is explicitely shutdown, sessions might froze (because 32 | of pending FIFO writes; the server not being able to reply, it blocks the FIFO 33 | operations and Kakoune hangs). In such a case, restarting the KTS server will 34 | see that some sessions were previously alive, and will recover by using the data 35 | for those sessions. 36 | 37 | That effectively allows for two main features: 38 | 39 | - Unfreezing in case of a KTS bug. 40 | - Upgrading KTS while Kakoune still runs. 41 | 42 | This is especially important if you are running other utilities, like a LSP 43 | server on a big project that takes time to index. 44 | -------------------------------------------------------------------------------- /docs/design/v0.0.1.md: -------------------------------------------------------------------------------- 1 | # Overall design 2 | 3 | > Disclaimer: this document is about v0.*, and the design has heavily evolved ever since. The content of this document 4 | > is then probably not up to date. 5 | 6 | `kak-tree-sitter` implements a client/server architecture. The idea is that a single instance of `kak-tree-sitter` 7 | should run as a server for a machine. Then, every Kakoune session can connect to the server and send requests. Whenever 8 | the server has computed the response for the request, it provides it back to Kakoune via the socket interface (`kak -p`) 9 | and the session identifier. 10 | 11 | For a given Kakoune session, the `kak-tree-sitter` server knows all of the buffers that are required to be parsed 12 | by `tree-sitter`. This allows efficient reusing of parsed data, which is an important aspect of `tree-sitter`: parse 13 | once, then reuse the parsed data whenever we need to parse again to minimize each delta update. 14 | 15 | ## Clientless run 16 | 17 | When a Kakoune session starts, it is recommended to run `kak-tree-sitter` — via your `kakrc` for instance. If 18 | `kak-tree-sitter` is not already running, either daemonized or not, an instance will be started. Then, `kak-tree-sitter` 19 | will add an entry for the Kakoune session and it will then be possible for clients of that session to start sending 20 | requests. 21 | 22 | > Note: if a Kakoune instance tries to connect to the (running) `kak-tree-sitter` server with a session name that has 23 | > already been registered, nothing happens, so there is no extra logic to do in the Kakoune configuration besides 24 | > just starting the server with the session name. 25 | 26 | If users do not want to start `kak-tree-sitter` inside their Kakoune configuration, it is still possible to start it 27 | like any other CLI application. They will have to source the `rc` files manually to be able to talk to the server, 28 | though. 29 | 30 | > Note: sourcing without starting the daemon is easy with `kak-tree-sitter --kakoune`. 31 | 32 | ## Client requests 33 | 34 | Client can then connect to the server. They have to provide a couple of information when performing a request: 35 | 36 | - The Kakoune session name, obviously. 37 | - The Kakoune client name, optional. Whenever a user wants to perform an operation that implies a Kakoune instance, 38 | the client is necessary. For instance, highlighting or selecting requires a client. 39 | 40 | ## Active and inactive sessions 41 | 42 | The default `rc` file, if sourced manually (or injected with `kak-tree-sitter --kakoune`), will install some important 43 | hooks to deal with sessions. At the first request a Kakoune does, its session will be recorded and marked as _active_ by 44 | `kak-tree-sitter`. 45 | 46 | A hook on `KakEnd` sends a special request to `kak-tree-sitter` to let it know that the session is over, marking it 47 | _inactive_ — it’s actually completely removed from the server. 48 | 49 | Once the server has an empty set of active session, it automatically exits. This allows to control precisely when the 50 | server should quit. 51 | 52 | > Note: this is only true if started from Kakoune, via `--kakoune`. If you start the server from the CLI, the server 53 | > remains up even if no session is connected to it. 54 | 55 | If a Kakoune session is killed and the `KakEnd` hook cannot run, the server will stay up until explicitely killed. The 56 | `kak-tree-sitter-req-stop` can be used to shutdwon the server. 57 | 58 | ## Automatic highlighting hooks 59 | 60 | A hook is automatically inserted on `WinCreate` (and transitively `WinDisplay`) to send a special request to the 61 | server to try and highlight the current buffer. That request is a two-step process: 62 | 63 | 1. A request of type `try_highlight` is sent to the server with the content of `%opt{kts_lang}`. 64 | 2. If `%opt{kts_lang}` has an associated grammar and highlight queries (highlights, locals, injections, etc.), then 65 | the server sends back some more Kakoune commands to run. Otherwise, it just does nothing and the process ends there. 66 | 3. If the server sent back highlighting commands to run, they are executed and install a `buffer` local set of hooks to 67 | highlight the buffer in the current session. Those hooks will react to user input to automatically re-highlight the 68 | buffer. 69 | 70 | ## `ktsctl`, the companion controller of `kak-tree-sitter` 71 | 72 | `ktsctl` is the controller CLI of `kak-tree-sitter`. It allows to run a variety of operations on the server and 73 | especially its runtime configuration. 74 | 75 | ## Grammars and queries sources 76 | 77 | By default, `kak-tree-sitter` ships with no tree-sitter grammar and query. This is a deliberate choice. Instead of 78 | statically compiling with them (and then forcing users to use a specific version of a grammar and queries), 79 | `kak-tree-sitter` dynamically loads grammars and runs queries at runtime. Both are located in 80 | `$XDG_DATA_DIR/kak-tree-sitter/{grammars,queries}`. 81 | 82 | Users have the choice to populate those directories by themselves, or use `ktsctl` to fetch, compile, link and install 83 | grammars / queries. 84 | 85 | An important aspect, though: grammars and especially queries are a best effort in `kak-tree-sitter`. What 86 | that means is that, even though `ktsctl` will fetch and install things for you, the _default source_ (i.e. where to 87 | fetch grammars and queries) is completely arbritrary. We default to 88 | [https://gituhb.com/tree-sitter](https://github.com/tree-sitter) for famous grammars and 89 | [https://github.com/helix-editor/helix](https://github.com/helix-editor/helix) for queries, but that is not an 90 | obligation. 91 | 92 | ## Colorschemes 93 | 94 | When highlighting a buffer, `kak-tree-sitter` traverses tree-sitter capture groups and transforms them to make them 95 | compatible with Kakoune. It does a couple of simple transformations: 96 | 97 | 1. For a given capture group — e.g. `variable.other.member`, it replaces dots with underscores — e.g. 98 | `variable_other_member`. 99 | 2. Then, it prepends `ts_` — e.g. `ts_variable_other_member`. 100 | 3. The final face `ts_variable_other_member` is used in highlighting regions of the buffer. 101 | 102 | Face definitions is currently performed in the `rc` file. If you notice a missing face, please open a PR to add it to 103 | the list. It is possible to disable inserting the faces in the `rc` file if your coloscheme defines those faces. 104 | 105 | In terms of what we define a face to, this is an on-going topic, but the idea is: 106 | 107 | 1. Prevent using absolute values, like `rgb` strings, etc. 108 | 2. Prefer linking to well known faces, such as colors or anything described in `:doc faces`. 109 | 3. The face should be documented in the [doc/faces.md](../doc/faces.md) document. That is important so that colorschemes 110 | that decide to support `kak-tree-sitter` know exactly which faces they must override. 111 | 112 | ## Configuration 113 | 114 | Most of the configuration should be done in `$XDG_CONFIG_DIR/kak-tree-sitter/config.toml`. The configuration options 115 | are detailed in a specific document (TODO: which one?). 116 | -------------------------------------------------------------------------------- /docs/man/JSX.md: -------------------------------------------------------------------------------- 1 | JSX grammars and queries are a bit weird, as they are not part of a specific language (i.e. `kts_lang`), but can work 2 | with Javascript, Typescript, etc. For this reason, extra setup is required. This page explains how to setup your 3 | configuration. 4 | 5 | > Note: this grammar / query set is incompatible with `javascript`. If you plan on using both Javascript and JSX, you 6 | > should only install the `jsx` grammar, as it also can work with regular Javascript files. See below for the setup. 7 | 8 | * [Fetch the grammar and queries](#fetch-the-grammar-and-queries) 9 | * [Patch the grammar](#fetch-the-grammar) 10 | * [Compile and install](#compile-and-install) 11 | * [Useful hook](#useful-hook) 12 | 13 | # Fetch the grammar and queries 14 | 15 | ```bash 16 | ktsctl -f jsx 17 | ``` 18 | 19 | # Patch the grammar 20 | 21 | This is the tricky part. You need to replace all `javascript` occurrences with `jsx`. Go in 22 | `$XDG_DATA_DIR/ktsctl/grammars/javascript` (or `$TMPDIR/ktsctl/grammars/javascript` on macOS if you do not have the XDG 23 | environment variables), and run this: 24 | 25 | ```bash 26 | grep -rl 'javascript' . | xargs sed -i '' -e 's/javascript/jsx/g' 27 | ``` 28 | 29 | # Compile and install 30 | 31 | Simply compile and install normally as you would do with `ktsctl`: 32 | 33 | ```bash 34 | ktsctl -ci jsx 35 | ``` 36 | 37 | # Useful hook 38 | 39 | If you still want to work with regular Javascript files, then you need to tell Kakoune to interpret them as if they were 40 | using the JSX grammars. You can do it with a simple hook translating `kts_lang=javascript` to `kts_lang=jsx`, such as: 41 | 42 | ```bash 43 | hook global BufSetOption kts_lang=(javascript|typescript) %{ 44 | eval %sh{ 45 | case $kak_bufname in 46 | (*\.jsx) echo "set-option buffer kts_lang jsx";; 47 | (*\.tsx) echo "set-option buffer kts_lang tsx";; 48 | esac 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/man/README.md: -------------------------------------------------------------------------------- 1 | # User manual 2 | 3 | This is the official user manual. You may want to visit the following links to 4 | know more. 5 | 6 | - [Getting started](getting-started.md) 7 | - [How to install](how-to-install.md) 8 | - [Usage](usage.md) 9 | - [Configuration](configuration.md) 10 | - [ktsctl](ktsctl.md) 11 | - [Features](features.md) 12 | - [Highlighting](highlighting.md) 13 | - [Text-objects](text-objects.md) 14 | - [Tweaking](tweaking.md) 15 | - [Frequently Asked Questions](faq.md) 16 | -------------------------------------------------------------------------------- /docs/man/commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | Commands are separated into three categories: 4 | 5 | - [Controlling kak-tree-sittter](#controlling-kak-tree-sitter) 6 | - [Highlighting](#highlighting) 7 | - [Text-objects](#text-objects) 8 | 9 | ## Controlling kak-tree-sitter 10 | 11 | | Command | Description | 12 | | ------- | ----------- | 13 | | `kak-tree-sitter-req-enable` | Send a request to enable integration with `kak-tree-sitter`. | 14 | | `kak-tree-sitter-req-stop` | Send a request to make `kak-tree-sitter` quit. | 15 | | `kak-tree-sitter-req-reload` | Send a request to make `kak-tree-sitter` reload its configuration, grammars and queries. | 16 | | `kak-tree-sitter-set-lang` (hidden) | Hidden function used to set `%opt{kts_lang}`. See [Kakoune interaction](Kakoune-interaction) for further information | 17 | 18 | ## Highlighting 19 | 20 | | Command | Description | 21 | | ------- | ----------- | 22 | | `kak-tree-sitter-highlight-buffer` | Force a highlight request on the current buffer | 23 | 24 | ## Text-objects 25 | 26 | | Command | Description | 27 | | ------- | ----------- | 28 | | `kak-tree-sitter-req-text-objects ` | Alter every selections by matching `` according to ``. See [the text-objects section](./Text-objects.md). | 29 | | `kak-tree-sitter-req-object-text-objects ` | Alter every selections by matching `` in _object_ mode. See [the text-objects section](./Text-objects.md). | 30 | | `kak-tree-sitter-req-nav ` | Alter every selections by navigating in the `` direction. See [the navigation section](./Text-objects.md#navigation). | 31 | -------------------------------------------------------------------------------- /docs/man/completions.md: -------------------------------------------------------------------------------- 1 | # Shell completions 2 | 3 | Both `kak-tree-sitter` and `ktsctl` ship with completions for some shells. This 4 | document lists supported shells and how to install the completions — if that’s 5 | not already handled by your package manager. 6 | 7 | ## Nushell 8 | 9 | Completions are located [here](../../completions/kak-tree-sitter.nu). You should 10 | source that file in your `config.nu`. For instance: 11 | 12 | ```nu 13 | source /docs/completions/kak-tree-sitter.nu 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/man/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Both `kak-tree-sitter` and `ktsctl` ship with a default configuration. It is 4 | possible to override the default options via the user configuration. 5 | 6 | The `$XDG_CONFIG_HOME/kak-tree-sitter/config.toml` contains the user 7 | configuration of both `kak-tree-sitter` and `ktsctl`. If you want to tweak 8 | something, you can have a look at the 9 | [default configuration file](https://github.com/hadronized/kak-tree-sitter/tree/master/kak-tree-sitter-config/default-config.toml) 10 | to know which path and values to pick from. 11 | 12 | > The user and default configurations get merged, so you do not have to copy the 13 | > default configuration to tweak it. 14 | 15 | # Option paths 16 | 17 | ## `features` 18 | 19 | This section contains enabled/disabled features. You can enable or disable a 20 | given feature if you are not interested in it. 21 | 22 | List of features: 23 | 24 | - `highlighting`: enable highlighting. If set to `false`, can be overridden on 25 | the CLI with `--with-highlighting`. _Default: `true`_. 26 | - `text_objects`: enable text-objects user modes and mappings. If set to 27 | `false`, can be overridden on the CLI with `--with-text-objects`. _Default: 28 | `true`_. 29 | 30 | ## `highlight.groups` 31 | 32 | The `highlight` section contains a single list, `groups`, which is used to list 33 | every capture groups used by language queries. If you install a language with 34 | queries containing new capture groups not already listed there, you need to add 35 | them at the end of the list. 36 | 37 | > Please consider contributing if you find a hole / missing capture group. 38 | 39 | ## `language` 40 | 41 | The `language` table contains language-keyed configuration — e.g. 42 | `language.rust`. Every language-keyed configuration contains more objects. 43 | 44 | - `remove_default_highlighter`, for removing the default highlighter set by the 45 | Kakoune distribution when enabling `kak-tree-sitter` support in a buffer. 46 | - `grammar`, for defining a grammar. 47 | - `queries`, for defining queries. 48 | 49 | ### `language..remove_default_higlighter` 50 | 51 | > Default value: `true` 52 | 53 | Remove the default highlighter set by the Kakoune “standard library” (i.e. 54 | `window/`). For instance, for `rust` filetypes, the default highlighter is 55 | `window/rust`. Setting this option to `true` will remove this highlighter, which 56 | is almost always wanted (otherwise, the highlighting from KTS might not be 57 | applied properly). 58 | 59 | Some languages might have an incomplete tree-sitter support; in such a case, you 60 | might not want to remove the default highlighter. Set this option to `false` in 61 | such cases, then. 62 | 63 | ### `language..grammar` 64 | 65 | This section contains various information about how to fetch, compile and link a 66 | grammar: 67 | 68 | - `source`: the source from where to pick the grammar; see the [Sources](#sources) section. 69 | - `path`: path where to find the various source files. Should always be `src` 70 | but can require adjustments for monorepositories. 71 | - `compile`: compile command to use. Should always be `cc`. 72 | - `compile_args`: arguments to pass to `compile` to compile the grammar. 73 | - `compile_flags`: optimization / debug flags. 74 | - `link`: link command to use. Should alwas be `cc`. 75 | - `link_args`: arguments to pass to `link` to link the grammar. 76 | - `link_flags`: optimization / debug / additional libraries to link flags. 77 | 78 | ### `language..queries` 79 | 80 | This section provides the required data to know how to fetch queries. 81 | 82 | - `source`: optional source from where to pick the queries; see the 83 | [Sources](#sources) section. If you omit it, the same `source` object is used 84 | for both the grammar and queries. 85 | - `path`: path where to find the queries (the `.scm` files) directory. 86 | 87 | # Sources 88 | 89 | Sources are a way to provide information from where runtime resources come from. 90 | We currently support two sources: 91 | 92 | - Local paths (`local.path`). 93 | - Git repositories (`git`), which is an object containing the following fields: 94 | - `url`: the URL to fetch from. Will use `git clone`. 95 | - `pin`: _pin ref_, such as a commit, branch name or tag. 96 | 97 | If you decide to use a `git` source: 98 | 99 | - Grammars must be _fetched_, _compiled_ and _installed_. `ktsctl` can do that 100 | automatically for you, provided you have 101 | the right configuration, by using the appropriate commands. See the 102 | documentation of [ktsctl](ktsctl.md). 103 | - Queries must be _fetched_ and _installed_, the same way as with grammars. 104 | - When you decide to install a “language”, both the grammars and queries might 105 | be fetched, compiled and installed if the configuration requires both to be. 106 | Hence, a single CLI command should basically do everything for you. 107 | 108 | If you decide to use a `local` source, **`ktsctl` will do nothing for you** and 109 | will simply display a message explaining that it will use a path. Nothing will 110 | be fetched, compiled nor installed. It’s up to you to do so. 111 | 112 | For users installing `ktsctl` by using a binary release or compiling it 113 | themselves, the default configuration (which uses `git` sources) is enough. 114 | However, if you ship with a distributed set of grammars and queries, you might 115 | want to override the languages’ configurations and use `local` sources. You can 116 | also mix them: a `git` source for the grammar, and a `local` one for the 117 | queries. It’s up to you. 118 | 119 | # Next 120 | 121 | You can have a look at [ktsctl] and [features] to start exploring what you can do! 122 | 123 | [ktsctl]: ktsctl.md 124 | [features]: features.md 125 | -------------------------------------------------------------------------------- /docs/man/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## I don’t want to deal with options; give me a oneliner to install a language 4 | 5 | ```sh 6 | ktsctl sync yaml 7 | ``` 8 | 9 | ## How do I install all the languages at once? 10 | 11 | ```sh 12 | ktsctl sync -a 13 | ``` 14 | 15 | ## Something broke and there is no highlighting anymore 16 | 17 | You can have a look at the log files in 18 | `$XDG_RUNTIME_DIR/kak-tree-sitter/{stdout.txt,stderr.txt}` and open an issue. 19 | -------------------------------------------------------------------------------- /docs/man/features.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | This document presents features planned by the project and their status. 4 | 5 | | Feature | Description | Status | Available since | Default config | CLI flag to enable | 6 | | --- | --- | --- | --- | --- | --- | 7 | | [Highlighting] | Asynchronous automatic highlighting of session buffers. | **Implemented** | `v0.2` | `true` | `--with-highlighting` | 8 | | [Text-objects] | Modify Kakoune selections with text-objects (`function.inside`, `parameter.around`, etc.). | **Implemented** | `v0.6` | `true` | Default, and `--with-text-objects` for additional setup | 9 | | Indents | Automatically indent your buffer. | Not started | | | `--with-indenting` | 10 | | Indent guidelines | Display a guideline showing the level of indentation left to lines. | Not started | | | `--with-indent-guidelines` | 11 | 12 | [Highlighting]: highlighting.md 13 | [Text-objects]: text-objects.md 14 | -------------------------------------------------------------------------------- /docs/man/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | Welcome! If you are reading this page, it is likely it is the first time you 4 | hear about this project. First and foremost, thank you for considering using 5 | this project. This section will provide you with a bunch of explanations about 6 | how the project is made, how it works, what you should expect and what you 7 | should not. 8 | 9 | ## Prerequisites 10 | 11 | As the name implies, this project bridges [tree-sitter] and [Kakoune]. You do 12 | not have to know anything about the former, but obviously, you need to have 13 | [Kakoune] installed. The rest is handled by this project. 14 | 15 | ## What is this? 16 | 17 | This project bridges [tree-sitter] and [Kakoune] via a server, that can be run 18 | on the CLI or inside [Kakoune] as a daemon. The server is unique to your 19 | machine, so it doesn’t matter that you start it from several Kakoune sessions, 20 | only one server will be up (this is implemented via a _PID file_). When the 21 | server is up, it remains up until the last session exits (if started from within 22 | Kakoune), or when you send it the `SIGINT` signal (_Ctrl-C_) if started on the 23 | CLI. 24 | 25 | When the server is started from Kakoune, it will install some commands and hooks 26 | to be able to communicate with the server. The configuration allows to decide 27 | which commands, hooks, user-modes etc. you want to have installed. 28 | 29 | A running server will wait for a session to send requests, and will reply 30 | asynchronously. That prevents your editor from freezing and allows the server to 31 | maintain a state / view of your per-session buffers. Because it uses 32 | [tree-sitter] under the hood, it will parse the content of your buffers as 33 | _trees_, allowing for fast operations. 34 | 35 | > Several features are supported / will be supported. You can refer to the 36 | > [Features](features.md) document to know more about what you can do with the 37 | > [tree-sitter] integration. 38 | 39 | In order to work, the server requires some runtime resources to be around. This 40 | project doesn’t ship with those resources, and you are free to use whichever 41 | sources you want. Two main kind of resources are needed: 42 | 43 | - _Grammars_, which are C dynamic libraries loaded at runtime by the server and 44 | used to parse your buffers and operate on trees. 45 | - _Queries_, which are [scm] strings allowing to _query_ parsed trees with a 46 | Lisp-like syntax. We use those to, mostly, unify the type of queries to all 47 | languages/grammars, such as highlighting, text-objects, indents, etc. 48 | 49 | You can either find those resources yourself, or use the provided controller 50 | companion of the server that provides that for free for you. See the 51 | [ktsctl](ktsctl.md) document. 52 | 53 | ## What you should expect 54 | 55 | The project was made in a way that supports most of the features that are 56 | natively available in other editors regarding [tree-sitter], such as [Helix]. It 57 | tries to be frustration-free, shipping with a default sane configuration; you 58 | should — and will probably — never modify the configuration, but if you need to, 59 | you can. The server and the [Kakoune] side of the project is made in a way that 60 | puts performance at top priority. Because we are using a UNIX approach, we try 61 | to minimize as much as possible process starts, memory writes, etc., approaching 62 | as near as possible memory-to-memory communication. Finally, the controller 63 | should do everything for you, provided you instruct what you want. You are not 64 | required to know _anything_ about [tree-sitter], nor how a query works or even 65 | how to compile a grammar. We do that for you. Just focus on what’s important; 66 | your own projects. 67 | 68 | ## What you should not expect 69 | 70 | The server is mostly about _transformations_. It doesn’t ship with _data_. 71 | Hence, you will not get grammars and queries shipped with the binary by default. 72 | It is important that you understand that; it works out of the box, but you still 73 | need to tell it what you want at first. 74 | 75 | Colorschemes support is hard. Using this project won’t automatically make your 76 | colorscheme look super nice. You will have to adapt or use a new, 77 | [tree-sitter]-powered colorscheme. The community tries its best to provide more 78 | and more colorschemes. You shall want to visit the 79 | [Highlighting](highlighting.md) document for further information. 80 | 81 | # Next 82 | 83 | Now that you are aware about what the project is, what it is not, and the 84 | prerequisites, you may want to go to [How to install](how-to-install.md). 85 | 86 | [tree-sitter]: https://tree-sitter.github.io/tree-sitter/ 87 | [Kakoune]: https://kakoune.org/ 88 | [scm]: https://en.wikipedia.org/wiki/SCM_(Scheme_implementation) 89 | [Helix]: https://helix-editor.com 90 | -------------------------------------------------------------------------------- /docs/man/highlighting.md: -------------------------------------------------------------------------------- 1 | # Highlighting 2 | 3 | In order to enable highlighting, the feature must be enabled, either in the 4 | configuration — `features.highlighting = true`, or by passing the 5 | `--with-highlighting` CLI flag when starting `kak-tree-sitter`. Then, **after 6 | `kak-tree-sitter --kakoune` is called**, you can use the `colorscheme` command 7 | with a tree-sitter compatible colorscheme. 8 | [See this section](#tree-sitter-enabled-colorschemes) for further information. 9 | 10 | ## Automatic highlighting of buffers 11 | 12 | Once the server is run, if your buffer can be highlighted, Kakoune will send 13 | (via hooks) requests to `kak-tree-sitter` to highlight your buffer. The current 14 | behavior is to highlight on `NormalIdle` and `InsertIdle` hooks. 15 | 16 | ## Interoperability with other highlighters 17 | 18 | Some other highlighters might operate on the content of the buffer, such as the 19 | `show-matching` one. If you are using such highlighters, because KTS 20 | highlighting is very likely to change _everything_ in the buffer, you should 21 | remove your global `add-highlighter`, and instead override the 22 | `tree-sitter-user-after-highlighter` command, adding your highlighter in it. 23 | 24 | For instance with `show-matching`: 25 | 26 | ```kak 27 | define-command -override tree-sitter-user-after-highlighter %{ 28 | add-highlighter -override buffer/show-matching show-matching 29 | } 30 | ``` 31 | 32 | > Note: you should be using `-override` when adding highlighters via 33 | > `tree-sitter-user-after-highlighter` because if you disconnect from KTS 34 | > (`tree-sitter-session-end`) and reconnect (`tree-sitter-session-begin`), the 35 | > highlighters will be inserted again. 36 | 37 | # Tree-sitter-enabled colorschemes 38 | 39 | Colorscheme support is provided by the various capture-groups taken from 40 | grammars and queries, which get translated to Kakoune `set-face` commands. You 41 | have two options: 42 | 43 | - Roam around and look for tree-sitter-enabled colorschemes. A starting point is 44 | [kakoune-tree-sitter-themes]. 45 | - Write your own colorscheme. You may want to read on this page. 46 | 47 | ## How to make your colorscheme tree-sitter aware 48 | 49 | The way tree-sitter colorschemes work is by calling `set-face` for the 50 | particular capture-groups you want to set the highlight for. Faces are organized 51 | in a _cascaded_ way, which means that by default, faces might have a parental 52 | relationship with others. For instance, the `ts_keyword_storage_modifier` face 53 | is defined as `ts_keyword_storage`, which is defined as `ts_keyword`. When a 54 | keyword doesn’t have any parent, by default, it’s set to `default`. 55 | 56 | > This behavior is _wanted_ and will make things look odd if you are not using a 57 | > proper tree-sitter colorschemes. 58 | 59 | It is recommended to set, at least, the top-level faces. If you want more 60 | granularity — for instance, a different color for `ts_keyword_storage` and 61 | `ts_keyword_storage_modifier` — you should specialize faces as well. 62 | 63 | You will need the list of faces to set, which can be find below in the 64 | [faces list section](#faces) 65 | 66 | ## Faces 67 | 68 | The following faces can and should be set in tree-sitter-enabled colorschemes. 69 | Cascaded faces inherit from their parent by default, so if you want all 70 | underlying faces to have the same highlight as their parent, you do not need to 71 | set them at all. 72 | 73 | - `ts_attribute` 74 | - `ts_comment` 75 | - `ts_comment_block` 76 | - `ts_comment_line` 77 | - `ts_conceal` 78 | - `ts_constant` 79 | - `ts_constant_builtin_boolean` 80 | - `ts_constant_character` 81 | - `ts_constant_character_escape` 82 | - `ts_constant_macro` 83 | - `ts_constant_numeric` 84 | - `ts_constant_numeric_float` 85 | - `ts_constant_numeric_integer` 86 | - `ts_constructor` 87 | - `ts_diff_plus` 88 | - `ts_diff_minus` 89 | - `ts_diff_delta` 90 | - `ts_diff_delta_moved` 91 | - `ts_error` 92 | - `ts_function` 93 | - `ts_function_builtin` 94 | - `ts_function_macro` 95 | - `ts_function_method` 96 | - `ts_function_special` 97 | - `ts_hint` 98 | - `ts_info` 99 | - `ts_keyword` 100 | - `ts_keyword_control` 101 | - `ts_keyword_conditional` 102 | - `ts_keyword_control_conditional` 103 | - `ts_keyword_control_directive` 104 | - `ts_keyword_control_import` 105 | - `ts_keyword_control_repeat` 106 | - `ts_keyword_control_return` 107 | - `ts_keyword_control_except` 108 | - `ts_keyword_control_exception` 109 | - `ts_keyword_directive` 110 | - `ts_keyword_function` 111 | - `ts_keyword_operator` 112 | - `ts_keyword_special` 113 | - `ts_keyword_storage` 114 | - `ts_keyword_storage_modifier` 115 | - `ts_keyword_storage_modifier_mut` 116 | - `ts_keyword_storage_modifier_ref` 117 | - `ts_keyword_storage_type` 118 | - `ts_label` 119 | - `ts_markup_bold` 120 | - `ts_markup_heading` 121 | - `ts_markup_heading_1` 122 | - `ts_markup_heading_2` 123 | - `ts_markup_heading_3` 124 | - `ts_markup_heading_4` 125 | - `ts_markup_heading_5` 126 | - `ts_markup_heading_6` 127 | - `ts_markup_heading_marker` 128 | - `ts_markup_italic` 129 | - `ts_markup_list_checked` 130 | - `ts_markup_list_numbered` 131 | - `ts_markup_list_unchecked` 132 | - `ts_markup_list_unnumbered` 133 | - `ts_markup_link_label` 134 | - `ts_markup_link_url` 135 | - `ts_markup_link_uri` 136 | - `ts_markup_link_text` 137 | - `ts_markup_quote` 138 | - `ts_markup_raw` 139 | - `ts_markup_raw_block` 140 | - `ts_markup_raw_inline` 141 | - `ts_markup_strikethrough` 142 | - `ts_namespace` 143 | - `ts_operator` 144 | - `ts_property` 145 | - `ts_punctuation` 146 | - `ts_punctuation_bracket` 147 | - `ts_punctuation_delimiter` 148 | - `ts_punctuation_special` 149 | - `ts_special` 150 | - `ts_spell` 151 | - `ts_string` 152 | - `ts_string_regex` 153 | - `ts_string_regexp` 154 | - `ts_string_escape` 155 | - `ts_string_special` 156 | - `ts_string_special_path` 157 | - `ts_string_special_symbol` 158 | - `ts_string_symbol` 159 | - `ts_tag` 160 | - `ts_tag_error` 161 | - `ts_text` 162 | - `ts_text_title` 163 | - `ts_type` 164 | - `ts_type_builtin` 165 | - `ts_type_enum_variant` 166 | - `ts_variable` 167 | - `ts_variable_builtin` 168 | - `ts_variable_other_member` 169 | - `ts_variable_parameter` 170 | - `ts_warning` 171 | 172 | [kakoune-tree-sitter-themes]: https://github.com/hadronized/kakoune-tree-sitter-themes 173 | -------------------------------------------------------------------------------- /docs/man/how-to-install.md: -------------------------------------------------------------------------------- 1 | # How to install 2 | 3 | The project contains two binaries: 4 | 5 | - `kak-tree-sitter`, which contains both the UNIX server and client. 6 | - `ktsctl`, the CLI controller. It is used to install various runtime resources 7 | related to `kak-tree-sitter`, such as grammars and queries. It is **optional** 8 | and you can install all the required resources manually if you’d rather like. 9 | 10 | Depending on your operating system, you should target a _release channel_ and a 11 | package manager to use. 12 | 13 | Additionally, completions are available for some shells. Feel free to visit 14 | the [completion](completions.md) document for installation. 15 | 16 | - [crates.io](#crates-io) 17 | 18 | ## crates.io 19 | 20 | This is the initial way of installing the project, and should only be used if 21 | you have no other option. 22 | 23 | ```sh 24 | cargo install kak-tree-sitter 25 | cargo install ktsctl 26 | ``` 27 | 28 | # Next 29 | 30 | Once you have installed the server and the optional controller, go on reading 31 | with [Usage](usage.md). 32 | -------------------------------------------------------------------------------- /docs/man/ktsctl.md: -------------------------------------------------------------------------------- 1 | # ktsctl 2 | 3 | `ktsctl` is the CLI controller for `kak-tree-sitter`. It provides features such 4 | as downloading, compiling / linking and installing grammars and queries. It is 5 | **optional** but highly recommended. 6 | 7 | You can configure it via the [configuration file](configuration.md). 8 | 9 | ## Commands 10 | 11 | > Prerequisites: 12 | > 13 | > - The `cc` compiler, which should default to `gcc` on Linux for instance. 14 | > - `git`, to download resources. 15 | > - Some tree-sitter grammars require C++, so you will need to have the `libstd++` library installed. 16 | 17 | Usage: `ktsctl `. Commands can be: 18 | 19 | - `fetch` used to fetch runtime resources. 20 | - `compile` used to compile runtime resources. You should have fetched them 21 | first though. 22 | - `install` used to install runtime resources. For grammars, you should compile 23 | them first. 24 | - `sync` used to synchronize resources — fetching, compiling, installing and 25 | checking pinned resources all at once. 26 | - `query`, used to get information about the languages configuration, installed 27 | - resources, etc. 28 | 29 | ### Managing resources 30 | 31 | You can fetch, compile and install resources manually with the `ktsctl fetch`, 32 | `ktsctl compile` and `ktsctl install` commands. However, a much more 33 | straightforward command to use is `ktsctl sync`, which does all of those at 34 | once, plus the additional feature of being idempotent. 35 | 36 | A typical one-liner to install a language is `ktsctl sync `, or 37 | 38 | > The list of language names you can installed can be found with the 39 | > [info command](#getting-information). 40 | 41 | For instance, to fetch, compile and install the grammar and queries for the Rust 42 | programming language: 43 | 44 | ```sh 45 | ktsctl sync rust 46 | ``` 47 | 48 | Once a language is installed, you will probably eventually update `ktsctl`, and 49 | if you haven’t set a specific `pin` for this language, you can benefit from the 50 | default shipped configuration updates by using the `sync` command again. 51 | 52 | #### Synchronizing everything 53 | 54 | If there was one command you should remember, it’s this one: 55 | 56 | ```sh 57 | ktsctl sync -a 58 | ``` 59 | 60 | It synchronizes everything. Basically, it will: 61 | 62 | - Ensure all the languages from the configuration are cloned / fetched. 63 | - If you don’t have a resource for one of them, the resource is fetched, 64 | compiled, installed. 65 | - If you do, the resource is checked against the pinned version of the 66 | configuration. If it’s too old, a new version is fetched, compiled, etc. 67 | 68 | > **Important note**: for `--all`, the runtime directory might get completely 69 | > filled, as grammars weigh a lot. If you encounter error, you can cleanup the 70 | > runtime directory, and run the command again. 71 | 72 | ### Getting information 73 | 74 | The `query` command allows to get information about tree-sitter resources. As 75 | with `manage`, you can use `--help` to know what you can do, but here are some 76 | useful commands: 77 | 78 | - `ktsctl query rust` will provide information about a specific language 79 | (here Rust). It will print out various configuration options, as well as 80 | whether the grammar and queries are installed. 81 | - `ktsctl query -a` will display a short summary for all available and 82 | recognized languages (based on the configuration). 83 | 84 | ## By-passing `ktsctl` and using your own runtime resources 85 | 86 | If you do not want to get into the rabbit hole of writing your own grammars and 87 | queries, you will most likely want to reuse datasets from other projects, such 88 | as the GitHub repositories of the grammars themselves (for instance, 89 | [tree-sitter-rust] for Rust), [Neovim] or [Helix]. 90 | 91 | Whatever you decide to use, you need to update your 92 | [user configuration](configuration.md) file according to the languages, grammars 93 | and queries. **However**, as explained in the linked section, 99% of people will 94 | just be satisfied with the default settings shipped with `kak-tree-sitter`’s 95 | `default-config.toml`, which you can find at the root of the repository, 96 | [here](https://github.com/hadronized/kak-tree-sitter/tree/master/kak-tree-sitter-config/default-config.toml). 97 | That configuration file is shipped with the binary (`kak-tree-sitter`), so you 98 | do not need to copy it; just add what you need! 99 | 100 | # A note for release channels 101 | 102 | Release channels ([AUR], [brew], etc.) have the right to ship the `default-config.toml` file (or any of their liking) 103 | along with both binaries. It is highly recommended that they follow SemVer (if they bundle `kak-tree-sitter v0.4.3`, 104 | they should be named `kak-tree-sitter-0.4.3` for instance). However, adding an extra number for the bump of the 105 | `default-config.toml` could be a good idea (so `kak-tree-sitter-0.4.3-1`, `kak-tree-sitter-0.4.3-2`, etc.). 106 | 107 | [tree-sitter-rust]: https://github.com/tree-sitter/tree-sitter-rust/tree/master/queries 108 | [Neovim]: https://github.com/nvim-treesitter/nvim-treesitter/tree/master/queries 109 | [Helix]: https://github.com/helix-editor/helix/tree/master/runtime/queries 110 | [AUR]: https://aur.archlinux.org 111 | [brew]: https://brew.sh 112 | -------------------------------------------------------------------------------- /docs/man/text-objects.md: -------------------------------------------------------------------------------- 1 | # Text-objects 2 | 3 | Text-objects are automatically supported, but a default, feature-full setup is 4 | available by passing the `--with-text-objects` flag to `kak-tree-sitter` when 5 | starting. That will create several user-modes, among the top-level `tree-sitter` 6 | mode (the others are accessible through it), and it will put some mappings in 7 | _object_ mode: 8 | 9 | - `a` for arguments. 10 | - `f` for functions. 11 | - `t` for types. 12 | - `T` for tests. 13 | 14 | If you want to customize and create your own mappings, it’s advised to read 15 | [Commands](./commands.md) to know which commands to call. 16 | 17 | ## Capture groups 18 | 19 | What people call “text-objects” are, in **tree-sitter**, referred to as 20 | _capture groups_. Even though everyone can use whatever they want, there are, at 21 | some extent, some standards. For text-objects, capture groups are expressed as 22 | `.`, where `` is either `inside` or `around`, and `` 23 | is the type of object you want to match against (`class`, `function`, 24 | `parameter`, etc.). 25 | 26 | Hence, a capture group to pattern _inside_ (i.e. bodies) of functions is 27 | `function.inside`. Matching on whole functions, including the signature, is 28 | `function.around`. 29 | 30 | ## Operational modes 31 | 32 | `kak-tree-sitter` has the concept of _operational modes_. When matching against 33 | a capture-group (e.g. `function.inside`), we still don’t know exactly what to 34 | do; do we want to select the next one? the previous one? extend to the next 35 | function? select inside the current function? etc. 36 | 37 | All those options are encoded as operational modes. There are many: 38 | 39 | - `search_next`: search for the next text-object after the cursor. Similar to 40 | `/`. 41 | - `search_prev`: search for the previous text-object before the cursor. Similar 42 | to ``. 43 | - `search_extend_next`: search and extend to the next text-object after the 44 | cursor. Similar to `?`. 45 | - `search_extend_prev`: search and extend to the previous text-object before the 46 | cursor. Similar to ``. 47 | - `find_next`: select onto the next text-object after the cursor. Similar to 48 | `f`. 49 | - `find_prev`: select onto the previous text-object before the cursor. Similar 50 | to ``. 51 | - `extend_next`: extend onto the next text-object after the cursor. Similar to 52 | `F`. 53 | - `extend_prev`: extend onto the previous text-object before the cursor. Similar 54 | to ``. 55 | 56 | ## Navigation 57 | 58 | Additionally to text-objects, you also get _tree-sitter-based navigation_. The 59 | way it works is by first selecting a node and then moving into a semantic 60 | direction. 61 | 62 | > As with regular text-objects, navigation is applied to **all selections**, not 63 | > only the primary one. 64 | 65 | The following directions are currently implemented: 66 | 67 | - `parent`: parent of the current node. 68 | - `first_child`: first child of the current node. 69 | - `last_child`: last child of the current node. 70 | - `first_sibling`: first sibling of the current node. 71 | - `last_sibling`: last sibling of the current node. 72 | - `prev_sibling`: previous sibling of the current node. Can include cousins. 73 | - `next_sibling`: next sibling of the current node. Can include cousins. 74 | 75 | The `tree-sitter` user-mode contains some mappings to start experimenting with 76 | navigation. 77 | 78 | If you would rather write your own commands, you need to use the 79 | `tree-sitter-nav ` command. `` is one of the items described above as 80 | a string — except for directions that can include cousins. In such a case, you 81 | need to turn the direction into an object with a single attribute, `cousin`, 82 | which is of type `bool`. Setting `cousin` to `true` will allow prev/next sibling 83 | navigation to “jump” over parents and try to go to cousins instead of direct 84 | siblings. 85 | 86 | ### Navigation examples 87 | 88 | - `tree-sitter-nav '"parent"'`: go to the parent of the current selections. 89 | - `tree-sitter-nav "'{ ""prev_sibling"": { ""cousin"": false } }'"`: go to the 90 | previous sibling. 91 | 92 | ### Bonus: sticky navigation 93 | 94 | A handy `tree-sitter-nav-sticky` user-mode is available to navigate without 95 | having to re-enter the user-mode. If you run the server with text-objects 96 | support, the mode is available via `T`. 97 | -------------------------------------------------------------------------------- /docs/man/tweaking.md: -------------------------------------------------------------------------------- 1 | # Tweaking 2 | 3 | If you are using the 4 | [provided config.toml](https://github.com/hadronized/kak-tree-sitter/tree/master/kak-tree-sitter-config/default-config.toml), 5 | some languages might require more setup. They are listed in this section. 6 | 7 | - [JSX](#jsx] 8 | 9 | ## JSX 10 | 11 | JSX grammars and queries are a bit weird, as they are not part of a specific 12 | language (i.e. `kts_lang`), but can work with Javascript, Typescript, etc. For 13 | this reason, extra setup is required. This page explains how to setup your configuration. 14 | 15 | > Note: this grammar / query set is incompatible with `javascript`. If you plan 16 | > on using both Javascript and JSX, you should only install the `jsx` grammar, 17 | > as it also can work with regular Javascript files. See below for the setup. 18 | 19 | - [Fetch the grammar and queries](#fetch-the-grammar-and-queries) 20 | - [Patch the grammar](#fetch-the-grammar) 21 | - [Compile and install](#compile-and-install) 22 | - [Useful hook](#useful-hook) 23 | 24 | ### Fetch the grammar and queries 25 | 26 | ```bash 27 | ktsctl -f jsx 28 | ``` 29 | 30 | ### Patch the grammar 31 | 32 | This is the tricky part. You need to replace all `javascript` occurrences with 33 | `jsx`. Go in `$XDG_DATA_DIR/ktsctl/grammars/javascript` (or 34 | `$TMPDIR/ktsctl/grammars/javascript` on macOS if you do not have the XDG 35 | environment variables), and run this: 36 | 37 | ```bash 38 | grep -rl 'javascript' . | xargs sed -i '' -e 's/javascript/jsx/g' 39 | ``` 40 | 41 | ### Compile and install 42 | 43 | Simply compile and install normally as you would do with `ktsctl`: 44 | 45 | ```bash 46 | ktsctl -ci jsx 47 | ``` 48 | 49 | ### Useful hook 50 | 51 | If you still want to work with regular Javascript files, then you need to tell 52 | Kakoune to interpret them as if they were using the JSX grammars. You can do it 53 | with a simple hook translating `tree_sitter_lang=javascript` to 54 | `tree_sitter_lang=jsx`, such as: 55 | 56 | ```bash 57 | hook global BufSetOption tree_sitter_lang=(javascript|typescript) %{ 58 | eval %sh{ 59 | case $kak_bufname in 60 | (*\.jsx) echo "set-option buffer tree_sitter_lang jsx";; 61 | (*\.tsx) echo "set-option buffer tree_sitter_lang tsx";; 62 | esac 63 | } 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/man/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | `kak-tree-sitter` is fairly straightforward to use. You can run it as a 4 | standalone server by running the following command in a terminal: 5 | 6 | ```sh 7 | kak-tree-sitter --server 8 | ``` 9 | 10 | Or you can run a similar command directly in your _kakrc_: 11 | 12 | ```kakrc 13 | eval %sh{ kak-tree-sitter -dks --init $kak_session } 14 | ``` 15 | 16 | When you start from the command line, the server will wait for sessions to 17 | connect and will persist even if no more Kakoune session exists. If you start 18 | from within Kakoune, the last session exiting will cause the server to stop and 19 | exit. 20 | 21 | > For more information about the command line interface and the possible 22 | > configuration options you have, please refer to the 23 | > [Kakoune configuration section](Kakoune-config.md). 24 | 25 | Wherever you start the server in your configuration, ensure that you do it 26 | **before picking your colorscheme**, because some Kakoune commands are injected 27 | and add face definitions. If your theme supports some of those faces, you don’t 28 | want them to be overriden by the default values of `kak-tree-sitter`. 29 | 30 | Some explanations: 31 | 32 | - `kak-tree-sitter` is the binary server. Refer to 33 | [the installation section](install.md) if you haven’t installed it yet. 34 | - `-d --daemon` starts the server in daemon mode. That means that if you start 35 | several sessions, the first session can exit before the new one. You should 36 | always use this flag when starting the server from Kakoune. 37 | - `-k --kakoune` is used when starting from Kakoune to log to its `*debug*` 38 | buffer, and is required to start the server from Kakoune anyway. 39 | - `-s --server` starts the server. 40 | - `--init ` tells the server to initiate a special request to get all 41 | the required configuration to communicate with the started server. Some 42 | important hooks are inserted, as well as face definitions and some internal 43 | options used to highlight your buffers). This flag writes directly to Kakoune 44 | without using any async operations, so this should only be used once in your 45 | config when a session starts. 46 | 47 | ## Feature picking 48 | 49 | There are more flags available to use. Refer to the [Features](features.md) 50 | document to know which flag to use to enable which features. If you do not pick 51 | any feature, some default ones are enabled for you for a default experience. 52 | 53 | ## Set the logging level 54 | 55 | Whether you are starting the server from Kakoune or from the CLI, you might 56 | encounter issues. In such cases, it’s recommended to change the verbosity of 57 | logs to be able to provide more information. You can use the `-v --verbose` flag 58 | to do so. Accumulating will increase the verbosity: 59 | 60 | - `-v` will print error messages. 61 | - `-vv` will print error and warning messages. 62 | - `-vvv` will print error, warning and info messages. 63 | - `-vvvv` will print error, warning, info and debug messages. 64 | - `-vvvvv` will print error, warning, info, debug and trace messages. 65 | 66 | ### Logging backends 67 | 68 | When started from the CLI, logs will be written to _stdout_. When started from 69 | within Kakoune, logs will be written to the `*debug*` buffer. 70 | 71 | # Next 72 | 73 | You may want to read the [Configuration](configuration.md) document. 74 | -------------------------------------------------------------------------------- /kak-tree-sitter-config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v2.2.0 2 | 3 | ## Language support 4 | 5 | - Add hyprlang suport. [52fa98f](https://github.com/hadronized/kak-tree-sitter/commit/52fa98f) 6 | - Add taskwarrior support. [64cc98a](https://github.com/hadronized/kak-tree-sitter/commit/64cc98a) 7 | - Add ini support. [1484673](https://github.com/hadronized/kak-tree-sitter/commit/1484673) 8 | 9 | # v2.1.1 10 | 11 | ## Language support 12 | 13 | - Fix `c` queries specification. [e42001b](https://github.com/hadronized/kak-tree-sitter/commit/e42001b) 14 | 15 | # v2.1.0 16 | 17 | ## Language support 18 | 19 | - Add support for Koka. [6bab165](https://github.com/hadronized/kak-tree-sitter/commit/6bab165) 20 | 21 | # v2.0.0 22 | 23 | This release is needed for a small fix in the API for error handling purposes. 24 | The `ConfigError` now contains a `MissingLang` variant that is used when 25 | retrieving the configuration of a language 26 | (`Result`), while it used to be 27 | `Option`. 28 | 29 | This change has implication on `ktsctl`, but doesn’t have any on 30 | `kak-tree-sitter`. 31 | 32 | ## API 33 | 34 | - Move missing language error as part of `kak-tree-sitter-config`. [cd35f75](https://github.com/hadronized/kak-tree-sitter/commit/cd35f75) 35 | 36 | # v1.0.0 37 | 38 | - Fix tests. 39 | - Add features in the config. 40 | This allows to automatically set highlighting and text-objects by default, 41 | preventing users from having to pass `--with-highlighting` and 42 | `--with-text-objects` all the time. 43 | 44 | The CLI still has precedence. 45 | - Add astro to default config. 46 | - Update tree-sitter-llvm patch version. 47 | - Add LLVM config. 48 | - Update MSRV and dependencies. 49 | - Zig grammar / queries. 50 | - Move to sr.ht. 51 | - Add `nim` to default-config.toml 52 | 53 | # v0.5.0 54 | 55 | - Introduce user-only configuration. [fc7c5c6](https://github.com/hadronized/kak-tree-sitter/commit/fc7c5c6) 56 | - Introduce `ktsctl` sources. [e083aad](https://github.com/hadronized/kak-tree-sitter/commit/e083aad) 57 | 58 | # v0.4.0 59 | 60 | - Add `remove_default_highlighter` option [d78abc0](https://github.com/hadronized/kak-tree-sitter/commit/d78abc0) 61 | 62 | # v0.3.0 63 | 64 | - `` 65 | 66 | # v0.2.0 67 | 68 | - Introduce proper error handling. 69 | - Rework the config. 70 | - Most of the configuration doesn’t have a default value anymore, but a file is provided in the Git repository. 71 | - `uri_fmt` is replaced by `url` in both grammars and queries configuration. 72 | 73 | # v0.1.0 74 | 75 | - Initial release. 76 | -------------------------------------------------------------------------------- /kak-tree-sitter-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kak-tree-sitter-config" 3 | version = "2.2.0-dev" 4 | license = "BSD-3-Clause" 5 | authors = ["Dimitri Sabadie "] 6 | description = "Server between Kakoune and tree-sitter; configuration layer" 7 | keywords = ["tree-sitter", "kakoune"] 8 | categories = ["text-editors"] 9 | homepage = "https://github.com/hadronized/kak-tree-sitter" 10 | repository = "https://github.com/hadronized/kak-tree-sitter" 11 | readme = "README.md" 12 | edition = "2021" 13 | rust-version = "1.70.0" 14 | 15 | [dependencies] 16 | dirs = "5.0" 17 | log = "0.4" 18 | serde = { version = "1.0", features = ["derive"] } 19 | thiserror = "1.0.60" 20 | toml = "0.8.12" 21 | -------------------------------------------------------------------------------- /kak-tree-sitter-config/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /kak-tree-sitter-config/README.md: -------------------------------------------------------------------------------- 1 | # Configuration layer for `kak-tree-sitter` and `ktsctl` 2 | 3 | This crate contains the `Config` type used by `kak-tree-sitter` and `ktsctl` in a unified way. 4 | -------------------------------------------------------------------------------- /kak-tree-sitter-config/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::PathBuf}; 2 | 3 | use thiserror::Error; 4 | 5 | #[derive(Debug, Error)] 6 | pub enum ConfigError { 7 | #[error("no configuration directory known for your system; please adjust XDG_CONFIG_HOME")] 8 | NoConfigDir, 9 | 10 | #[error("cannot read configuration at {path}: {err}")] 11 | CannotReadConfig { path: PathBuf, err: io::Error }, 12 | 13 | #[error("cannot parse configuration: {err}")] 14 | CannotParseConfig { err: String }, 15 | 16 | #[error("missing configuration option: {opt}")] 17 | MissingOption { opt: String }, 18 | 19 | #[error("no configuration for language {lang}")] 20 | MissingLang { lang: String }, 21 | } 22 | 23 | impl ConfigError { 24 | pub fn missing_opt(opt: impl Into) -> Self { 25 | Self::MissingOption { opt: opt.into() } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kak-tree-sitter-config/src/source.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::ConfigError; 6 | 7 | /// Tree-sitter runtime resources sources. 8 | /// 9 | /// Sources can be local or remote. In the case of remote sources, we only support git repositories for now. 10 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 11 | #[serde(rename_all = "snake_case")] 12 | pub enum Source { 13 | Local { path: PathBuf }, 14 | Git { url: String, pin: String }, 15 | } 16 | 17 | impl Source { 18 | pub fn local(path: impl Into) -> Self { 19 | let path = path.into(); 20 | Self::Local { path } 21 | } 22 | 23 | pub fn git(url: impl Into, pin: impl Into) -> Self { 24 | let url = url.into(); 25 | let pin = pin.into(); 26 | Self::Git { url, pin } 27 | } 28 | } 29 | 30 | impl Source { 31 | pub fn merge_user_config(&mut self, user_source: UserSource) { 32 | match (self, user_source) { 33 | (self_, UserSource::Local { path }) => *self_ = Source::Local { path }, 34 | 35 | ( 36 | self_ @ Source::Local { .. }, 37 | UserSource::Git { 38 | url: Some(url), 39 | pin: Some(pin), 40 | }, 41 | ) => { 42 | *self_ = Source::Git { url, pin }; 43 | } 44 | 45 | ( 46 | Source::Git { 47 | url: ref mut self_url, 48 | pin: ref mut self_pin, 49 | }, 50 | UserSource::Git { url, pin }, 51 | ) => { 52 | if let Some(url) = url { 53 | *self_url = url; 54 | } 55 | 56 | if let Some(pin) = pin { 57 | *self_pin = pin; 58 | } 59 | } 60 | 61 | _ => (), 62 | } 63 | } 64 | } 65 | 66 | impl TryFrom for Source { 67 | type Error = ConfigError; 68 | 69 | fn try_from(source: UserSource) -> Result { 70 | match source { 71 | UserSource::Local { path } => Ok(Self::Local { path }), 72 | 73 | UserSource::Git { url, pin } => { 74 | let url = url.ok_or_else(|| ConfigError::missing_opt("url"))?; 75 | let pin = pin.ok_or_else(|| ConfigError::missing_opt("pin"))?; 76 | Ok(Self::Git { url, pin }) 77 | } 78 | } 79 | } 80 | } 81 | 82 | #[derive(Clone, Debug, Deserialize, Serialize)] 83 | #[serde(rename_all = "snake_case")] 84 | pub enum UserSource { 85 | Local { 86 | path: PathBuf, 87 | }, 88 | 89 | Git { 90 | url: Option, 91 | pin: Option, 92 | }, 93 | } 94 | 95 | impl UserSource { 96 | pub fn local(path: impl Into) -> Self { 97 | let path = path.into(); 98 | Self::Local { path } 99 | } 100 | 101 | pub fn git(url: impl Into>, pin: impl Into>) -> Self { 102 | let url = url.into(); 103 | let pin = pin.into(); 104 | Self::Git { url, pin } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /kak-tree-sitter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kak-tree-sitter" 3 | version = "1.1.2-dev" 4 | license = "BSD-3-Clause" 5 | authors = ["Dimitri Sabadie "] 6 | description = "Server between Kakoune and tree-sitter" 7 | keywords = ["tree-sitter", "kakoune"] 8 | categories = ["text-editors"] 9 | homepage = "https://github.com/hadronized/kak-tree-sitter/" 10 | repository = "https://github.com/hadronized/kak-tree-sitter/" 11 | readme = "../README.md" 12 | edition = "2021" 13 | rust-version = "1.70.0" 14 | 15 | [dependencies] 16 | chrono = "0.4" 17 | clap = { version = "4.5", features = ["derive"] } 18 | ctrlc = "3.4" 19 | daemonize = "0.5" 20 | dirs = "5.0" 21 | itertools = "0.12" 22 | #kak-tree-sitter-config = { version = ">=1.0.0, <3.0.0", path = "../kak-tree-sitter-config" } 23 | kak-tree-sitter-config = { version = "2.2.0-dev", path = "../kak-tree-sitter-config" } 24 | libc = "0.2" 25 | libloading = "0.8" 26 | log = "0.4" 27 | mio = { version = "0.8.11", features = ["net", "os-poll", "os-ext"] } 28 | serde = { version = "1.0", features = ["derive"] } 29 | serde_json = "1.0" 30 | simple_logger = "5" 31 | thiserror = "1.0" 32 | tree-sitter = "0.20" 33 | tree-sitter-highlight = "0.20" 34 | unicode-segmentation = "1.11" 35 | uuid = { version = "1.8.0", features = ["v4"] } 36 | 37 | [dev-dependencies] 38 | tree-sitter-rust = "0.20" 39 | -------------------------------------------------------------------------------- /kak-tree-sitter/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /kak-tree-sitter/build.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Write, process::Command}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let mut version = env!("CARGO_PKG_VERSION").to_owned(); 5 | if let Some(sha1) = git_sha1() { 6 | write!(&mut version, "-{sha1}")?; 7 | } 8 | 9 | println!("cargo:rustc-env=VERSION={version}"); 10 | 11 | Ok(()) 12 | } 13 | 14 | fn git_sha1() -> Option { 15 | Command::new("git") 16 | .args(["rev-parse", "--short", "HEAD"]) 17 | .output() 18 | .ok() 19 | .filter(|stdout| stdout.status.success()) 20 | .and_then(|stdout| String::from_utf8(stdout.stdout).ok()) 21 | .map(|hash| hash.trim().to_owned()) 22 | } 23 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Debug, Parser)] 4 | #[clap( 5 | author = "Dimitri Sabadie ", 6 | name = "kak-tree-sitter", 7 | version = env!("VERSION"), 8 | about = "A client/server interface between Kakoune and tree-sitter." 9 | )] 10 | pub struct Cli { 11 | /// Whether we start from Kakoune. 12 | /// 13 | /// This is mainly used to select a specific logger. 14 | #[clap(short, long)] 15 | pub kakoune: bool, 16 | 17 | /// Initiate the current session by injecting some rc. 18 | #[clap(long, value_name = "SESSION")] 19 | pub init: Option, 20 | 21 | /// Start the server, if not already started. 22 | #[clap(short, long)] 23 | pub server: bool, 24 | 25 | /// Try to daemonize, if not already done. 26 | #[clap(short, long)] 27 | pub daemonize: bool, 28 | 29 | /// Kakoune client to connect with, if any. 30 | #[clap(short, long)] 31 | pub client: Option, 32 | 33 | /// JSON-serialized request. 34 | #[clap(short, long)] 35 | pub request: Option, 36 | 37 | /// Verbosity. 38 | /// 39 | /// Can be accumulated to get more verbosity. Without this flag, logging is disabled. Then, for each applicaton of the 40 | /// flag, the obtained verbosity follows this order: error, warn, info, debug, trace. Thus, if you use -v, you will 41 | /// only get error messages. If you use -vv, you will also see warnings. The maximum verbosity is achieved with -vvvvv 42 | /// for trace logs. 43 | #[arg(short, long, action = clap::ArgAction::Count)] 44 | pub verbose: u8, 45 | 46 | /// Insert Kakoune code related to highlighting. 47 | /// 48 | /// Highlighting is supported mainly via hooks and commands that ping-ping between Kakoune and KTS. 49 | #[arg(long)] 50 | pub with_highlighting: bool, 51 | 52 | /// Insert Kakoune commands, user modes and mappings related to text-objects. 53 | /// 54 | /// Those are default and completely optional. It is advised to start with those and if further customization is 55 | /// needed, you shall not use this flag and craft your own user modes and mappings. 56 | /// 57 | /// Text-objects user-modes will be available via the 'tree-sitter' user-mode. 58 | #[arg(long)] 59 | pub with_text_objects: bool, 60 | } 61 | 62 | impl Cli { 63 | pub fn is_standalone(&self) -> bool { 64 | self.server && self.init.is_none() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/client.rs: -------------------------------------------------------------------------------- 1 | //! Client implementation. 2 | //! 3 | //! This module exports the [`Client`] type that is used to send requests to the 4 | //! server. 5 | 6 | use std::{ 7 | io::{self, Write}, 8 | os::unix::net::UnixStream, 9 | }; 10 | 11 | use crate::{error::OhNo, protocol::request::Request, server::resources::Paths}; 12 | 13 | /// Connected client (UNIX socket). 14 | #[derive(Debug)] 15 | pub struct Client { 16 | stream: UnixStream, 17 | } 18 | 19 | impl Client { 20 | pub fn connect(paths: &Paths) -> Result { 21 | let stream = UnixStream::connect(paths.socket_path()) 22 | .map_err(|err| OhNo::CannotConnectToServer { err })?; 23 | log::debug!("connected to KTS"); 24 | 25 | Ok(Self { stream }) 26 | } 27 | 28 | /// Convenient method to connect to the server and initiate a session. 29 | pub fn init_session(paths: &Paths, session: impl Into) -> Result<(), OhNo> { 30 | let mut client = Self::connect(paths)?; 31 | client.send(&Request::init_session(session)) 32 | } 33 | 34 | /// Asynchronously send a request. 35 | pub fn send(&mut self, req: &Request) -> Result<(), OhNo> { 36 | let bytes = serde_json::to_string(req).map_err(|err| OhNo::CannotSendRequest { 37 | err: err.to_string(), 38 | })?; 39 | 40 | loop { 41 | let r = self.stream.write_all(bytes.as_bytes()); 42 | match r { 43 | Err(err) => match err.kind() { 44 | io::ErrorKind::Interrupted => continue, 45 | _ => { 46 | return Err(OhNo::CannotSendRequest { 47 | err: err.to_string(), 48 | }) 49 | } 50 | }, 51 | 52 | _ => return Ok(()), 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::PathBuf}; 2 | 3 | use kak_tree_sitter_config::error::ConfigError; 4 | use log::SetLoggerError; 5 | use mio::Token; 6 | use thiserror::Error; 7 | use tree_sitter::{LanguageError, QueryError}; 8 | 9 | use crate::kakoune::buffer::BufferId; 10 | 11 | #[derive(Debug, Error)] 12 | pub enum OhNo { 13 | #[error("nothing to do; please either use --server or --request")] 14 | NothingToDo, 15 | 16 | #[error("no runtime directory")] 17 | NoRuntimeDir, 18 | 19 | #[error("cannot initialize logging: {err}")] 20 | LoggerInit { 21 | #[from] 22 | err: SetLoggerError, 23 | }, 24 | 25 | #[error("configuration error: {err}")] 26 | ConfigError { 27 | #[from] 28 | err: ConfigError, 29 | }, 30 | 31 | #[error("cannot create directory {dir}: {err}")] 32 | CannotCreateDir { dir: PathBuf, err: io::Error }, 33 | 34 | #[error("cannot create file {file}: {err}")] 35 | CannotCreateFile { file: PathBuf, err: io::Error }, 36 | 37 | #[error("cannot write to file {file}: {err}")] 38 | CannotWriteFile { file: PathBuf, err: io::Error }, 39 | 40 | #[error("cannot start daemon: {err}")] 41 | CannotStartDaemon { err: String }, 42 | 43 | #[error("cannot set SIGINT handler: {err}")] 44 | SigIntHandlerError { 45 | #[from] 46 | err: ctrlc::Error, 47 | }, 48 | 49 | #[error("cannot create FIFO: {err}")] 50 | CannotCreateFifo { err: String }, 51 | 52 | #[error("cannot open non-blocking FIFO: {err}")] 53 | CannotOpenFifo { err: io::Error }, 54 | 55 | #[error("cannot read FIFO: {err}")] 56 | CannotReadFifo { err: io::Error }, 57 | 58 | #[error("poll error: {err}")] 59 | PollError { err: io::Error }, 60 | 61 | #[error("cannot start server: {err}")] 62 | CannotStartServer { err: io::Error }, 63 | 64 | #[error("cannot load grammar for language {lang}: {err}")] 65 | CannotLoadGrammar { lang: String, err: String }, 66 | 67 | #[error("UNIX socket connection error: {err:?}")] 68 | UnixSocketConnectionError { err: io::Error }, 69 | 70 | #[error("UNIX socket read error: {err:?}")] 71 | UnixSocketReadError { err: io::Error }, 72 | 73 | #[error("invalid request {req}: {err}")] 74 | InvalidRequest { req: String, err: String }, 75 | 76 | #[error("cannot connect to server; is it running?: {err}")] 77 | CannotConnectToServer { err: io::Error }, 78 | 79 | #[error("cannot send request: {err}")] 80 | CannotSendRequest { err: String }, 81 | 82 | #[error("cannot parse buffer")] 83 | CannotParseBuffer, 84 | 85 | #[error("highlight error: {err}")] 86 | HighlightError { err: String }, 87 | 88 | #[error("unknown language: {lang}")] 89 | UnknownLang { lang: String }, 90 | 91 | #[error("unknown buffer: {id:?}")] 92 | UnknownBuffer { id: BufferId }, 93 | 94 | #[error("unknown buffer token: {tkn:?}")] 95 | UnknownToken { tkn: Token }, 96 | 97 | #[error("language error: {err}")] 98 | LangError { 99 | #[from] 100 | err: LanguageError, 101 | }, 102 | 103 | #[error("query error: {err}")] 104 | QueryError { 105 | #[from] 106 | err: QueryError, 107 | }, 108 | 109 | #[error("text-objects not supported")] 110 | UnsupportedTextObjects, 111 | 112 | #[error("no such {pattern} text-object query")] 113 | UnknownTextObjectQuery { pattern: String }, 114 | } 115 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/kakoune.rs: -------------------------------------------------------------------------------- 1 | pub mod buffer; 2 | pub mod rc; 3 | pub mod selection; 4 | pub mod session; 5 | pub mod text_objects; 6 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/kakoune/buffer.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// A unique way to identify a buffer. 4 | /// 5 | /// Currently tagged by the session name and the buffer name. 6 | #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] 7 | pub struct BufferId { 8 | session: String, 9 | buffer: String, 10 | } 11 | 12 | impl BufferId { 13 | pub fn new(session: impl Into, buffer: impl Into) -> Self { 14 | Self { 15 | session: session.into(), 16 | buffer: buffer.into(), 17 | } 18 | } 19 | 20 | pub fn session(&self) -> &str { 21 | &self.session 22 | } 23 | 24 | pub fn buffer(&self) -> &str { 25 | &self.buffer 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/kakoune/rc.rs: -------------------------------------------------------------------------------- 1 | //! rc file used by Kakoune to inject kak-tree-sitter commands. 2 | 3 | /// Main RC file. 4 | pub fn static_kak() -> &'static str { 5 | include_str!("../../rc/static.kak") 6 | } 7 | 8 | /// Text-objects related file. 9 | pub fn text_objects_kak() -> &'static str { 10 | include_str!("../../rc/text-objects.kak") 11 | } 12 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/kakoune/session.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | io::Write, 4 | process::{Command, Stdio}, 5 | }; 6 | 7 | use crate::{error::OhNo, protocol::response::Response}; 8 | 9 | /// Session tracker. 10 | /// 11 | /// Responsible for tracking sessions (by names). 12 | #[derive(Debug, Default)] 13 | pub struct SessionTracker { 14 | sessions: HashMap, 15 | } 16 | 17 | impl SessionTracker { 18 | pub fn is_empty(&self) -> bool { 19 | self.sessions.is_empty() 20 | } 21 | 22 | /// Check whether a session is already tracked. 23 | pub fn tracks(&self, session: &str) -> bool { 24 | self.sessions.contains_key(session) 25 | } 26 | 27 | pub fn track(&mut self, session: Session) { 28 | self.sessions.insert(session.name.clone(), session); 29 | } 30 | 31 | pub fn untrack(&mut self, session_name: impl AsRef) { 32 | self.sessions.remove(session_name.as_ref()); 33 | } 34 | 35 | pub fn sessions(&self) -> impl Iterator { 36 | self.sessions.keys().map(String::as_str) 37 | } 38 | } 39 | 40 | /// An (active) session. 41 | #[derive(Debug)] 42 | pub struct Session { 43 | name: String, 44 | } 45 | 46 | impl Session { 47 | /// Create a new [`Session`] for the given name. 48 | pub fn new(name: impl Into) -> Result { 49 | Ok(Self { name: name.into() }) 50 | } 51 | 52 | /// Send a response back to Kakoune. 53 | pub fn send_response(resp: Response) -> Result<(), OhNo> { 54 | let Some(data) = resp.to_kak() else { 55 | // FIXME: this is a weird situation where the [`Response`] doesn’t really 56 | // have any Kakoune counterpart; I plan on removing that ~soon 57 | return Ok(()); 58 | }; 59 | 60 | // spawn the kak -p process 61 | // TODO: we want to switch that from directly connecting to the UNIX socket 62 | let mut child = Command::new("kak") 63 | .args(["-p", resp.session()]) 64 | .stdin(Stdio::piped()) 65 | .spawn() 66 | .map_err(|err| OhNo::CannotSendRequest { 67 | err: err.to_string(), 68 | })?; 69 | let child_stdin = child 70 | .stdin 71 | .as_mut() 72 | .ok_or_else(|| OhNo::CannotSendRequest { 73 | err: "cannot pipe data to kak -p".to_owned(), 74 | })?; 75 | 76 | child_stdin 77 | .write_all(data.as_bytes()) 78 | .map_err(|err| OhNo::CannotSendRequest { 79 | err: err.to_string(), 80 | })?; 81 | 82 | child_stdin.flush().map_err(|err| OhNo::CannotSendRequest { 83 | err: err.to_string(), 84 | })?; 85 | 86 | child.wait().map_err(|err| OhNo::CannotSendRequest { 87 | err: format!("error while waiting on kak -p: {err}"), 88 | })?; 89 | 90 | Ok(()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/kakoune/text_objects.rs: -------------------------------------------------------------------------------- 1 | //! Text-object support. 2 | //! 3 | //! Requests use strings like `"function.inside"`, `"function.around"`, etc. to target specific capture groups. We do 4 | //! not provide a type for this, as the patterns are free and varies on the languages / grammars. 5 | //! 6 | //! However, operation modes are fixed and represent what to do with the matched parts of the buffer. For instance, 7 | //! [`OperationMode::Next`] will move each selection to the next matched capture group, etc. 8 | 9 | use serde::{Deserialize, Serialize}; 10 | 11 | use crate::kakoune::selection::SelectMode; 12 | 13 | /// Text-objects can be manipulated in two different ways: 14 | /// 15 | /// - In object mode, to expand selections or replace them. 16 | /// - To shrink selections via selecting or splitting, as in `s`, `S`, etc. 17 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 18 | #[serde(rename_all = "snake_case")] 19 | pub enum OperationMode { 20 | /// Search for the next text-object. 21 | /// 22 | /// Similar to `/`. 23 | SearchNext, 24 | 25 | /// Search for the previous text-object. 26 | /// 27 | /// Similar to ``. 28 | SearchPrev, 29 | 30 | /// Search-extend for the next text-object. 31 | /// 32 | /// Similar to `?`. 33 | SearchExtendNext, 34 | 35 | /// Search-extend for the previous text-object. 36 | /// 37 | /// Similar to ``. 38 | SearchExtendPrev, 39 | 40 | /// Find the next text-object. 41 | /// 42 | /// Similar to `f`. 43 | FindNext, 44 | 45 | /// Find the previous text-object. 46 | /// 47 | /// Similar to ``. 48 | FindPrev, 49 | 50 | /// Extend onto the next text-object. 51 | /// 52 | /// Similar to `F`. 53 | ExtendNext, 54 | 55 | /// Extend onto the previous text-object. 56 | /// 57 | /// Similar to ``. 58 | ExtendPrev, 59 | 60 | /// Select the text-object inside selections. 61 | /// 62 | /// Similar to `s`. 63 | Select, 64 | 65 | /// Object mode. 66 | /// 67 | /// This combines select mode with object flags. 68 | Object { mode: SelectMode, flags: String }, 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use crate::kakoune::{selection::SelectMode, text_objects::OperationMode}; 74 | 75 | #[test] 76 | fn deser() { 77 | assert_eq!( 78 | serde_json::from_str::("\"search_next\"").unwrap(), 79 | OperationMode::SearchNext 80 | ); 81 | assert_eq!( 82 | serde_json::from_str::("\"search_prev\"").unwrap(), 83 | OperationMode::SearchPrev 84 | ); 85 | assert_eq!( 86 | serde_json::from_str::("\"search_extend_next\"").unwrap(), 87 | OperationMode::SearchExtendNext 88 | ); 89 | assert_eq!( 90 | serde_json::from_str::("\"search_extend_prev\"").unwrap(), 91 | OperationMode::SearchExtendPrev 92 | ); 93 | assert_eq!( 94 | serde_json::from_str::("\"find_next\"").unwrap(), 95 | OperationMode::FindNext 96 | ); 97 | assert_eq!( 98 | serde_json::from_str::("\"find_prev\"").unwrap(), 99 | OperationMode::FindPrev 100 | ); 101 | assert_eq!( 102 | serde_json::from_str::("\"extend_next\"").unwrap(), 103 | OperationMode::ExtendNext 104 | ); 105 | assert_eq!( 106 | serde_json::from_str::("\"extend_prev\"").unwrap(), 107 | OperationMode::ExtendPrev 108 | ); 109 | 110 | assert_eq!( 111 | serde_json::from_str::( 112 | r#"{ "object": { "mode": "replace", "flags": "to_begin|to_end|inner" }}"# 113 | ) 114 | .unwrap(), 115 | OperationMode::Object { 116 | mode: SelectMode::Replace, 117 | flags: "to_begin|to_end|inner".to_owned() 118 | } 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/logging.rs: -------------------------------------------------------------------------------- 1 | //! Logging related module. 2 | 3 | use log::{Level, Log}; 4 | 5 | use crate::error::OhNo; 6 | 7 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 8 | pub enum Verbosity { 9 | #[default] 10 | Error, 11 | Warn, 12 | Info, 13 | Debug, 14 | Trace, 15 | Off, 16 | } 17 | 18 | impl Verbosity { 19 | pub fn from_count(count: u8) -> Self { 20 | match count { 21 | 0 => Self::Off, 22 | 1 => Self::Error, 23 | 2 => Self::Warn, 24 | 3 => Self::Info, 25 | 4 => Self::Debug, 26 | _ => Self::Trace, 27 | } 28 | } 29 | 30 | pub fn to_level(self) -> Option { 31 | match self { 32 | Verbosity::Error => Some(log::Level::Error), 33 | Verbosity::Warn => Some(log::Level::Warn), 34 | Verbosity::Info => Some(log::Level::Info), 35 | Verbosity::Debug => Some(log::Level::Debug), 36 | Verbosity::Trace => Some(log::Level::Trace), 37 | Verbosity::Off => None, 38 | } 39 | } 40 | } 41 | 42 | /// A logger that simply writes to a kakoune session. 43 | /// 44 | /// This logger is important when the binary is started from within Kakoune, as Kakoune interprets stdout. 45 | #[derive(Debug)] 46 | pub struct KakouneLogger { 47 | level: Level, 48 | } 49 | 50 | impl KakouneLogger { 51 | pub fn new(level: Level) -> Self { 52 | Self { level } 53 | } 54 | 55 | pub fn register(self) -> Result<(), OhNo> { 56 | log::set_max_level(self.level.to_level_filter()); 57 | log::set_boxed_logger(Box::new(self))?; 58 | 59 | Ok(()) 60 | } 61 | } 62 | 63 | impl Log for KakouneLogger { 64 | fn enabled(&self, metadata: &log::Metadata) -> bool { 65 | metadata.level() <= self.level 66 | } 67 | 68 | fn log(&self, record: &log::Record) { 69 | if !self.enabled(record.metadata()) { 70 | return; 71 | } 72 | 73 | let now = chrono::Utc::now(); 74 | eprintln!( 75 | "tree-sitter {time} ({target}) [{level}]: {args}", 76 | time = now.to_rfc3339(), 77 | target = record.target(), 78 | level = record.level(), 79 | args = record.args() 80 | ); 81 | } 82 | 83 | fn flush(&self) {} 84 | } 85 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/main.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | mod client; 3 | mod error; 4 | mod kakoune; 5 | mod logging; 6 | mod protocol; 7 | mod server; 8 | mod tree_sitter; 9 | 10 | use std::{fs::File, sync::Arc}; 11 | 12 | use clap::Parser; 13 | use cli::Cli; 14 | use error::OhNo; 15 | use kak_tree_sitter_config::Config; 16 | use logging::Verbosity; 17 | use mio::Poll; 18 | use protocol::request::Request; 19 | use server::{ 20 | resources::{Paths, ServerResources}, 21 | Server, 22 | }; 23 | 24 | use crate::{kakoune::rc, logging::KakouneLogger}; 25 | 26 | fn main() { 27 | if let Err(err) = start() { 28 | log::error!("fatal error: {err}"); 29 | std::process::exit(1); 30 | } 31 | } 32 | 33 | fn start() -> Result<(), OhNo> { 34 | let cli = Cli::parse(); 35 | 36 | // setup logger; if we start from kakoune, the logger implementation sends to 37 | // the *debug* buffer 38 | if let Some(level) = Verbosity::from_count(cli.verbose).to_level() { 39 | if cli.kakoune { 40 | KakouneLogger::new(level).register()?; 41 | } else { 42 | simple_logger::init_with_level(level)?; 43 | } 44 | } 45 | 46 | let config = Config::load_default_user()?; 47 | 48 | // inject rc if we start from Kakoune 49 | if cli.kakoune && cli.init.is_some() { 50 | println!("{}", rc::static_kak()); 51 | 52 | if cli.with_text_objects || config.features.text_objects { 53 | println!("{}", rc::text_objects_kak()); 54 | } 55 | } 56 | 57 | let paths = Paths::new()?; 58 | 59 | if let Some(request) = cli.request { 60 | // otherwise, regular client 61 | let req = Request::from_json(request)?; 62 | 63 | let mut client = client::Client::connect(&paths)?; 64 | client.send(&req)?; 65 | 66 | // if we sent the request from within Kakoune, we return nop command so that we can call the commands and print 67 | // errors along the way 68 | if cli.kakoune { 69 | println!("nop"); 70 | } 71 | 72 | return Ok(()); 73 | } 74 | 75 | if cli.server { 76 | if Server::is_server_running(&paths) { 77 | log::debug!("server already running"); 78 | 79 | if let Some(session) = cli.init { 80 | log::debug!("initiating first session {session}"); 81 | client::Client::init_session(&paths, session)?; 82 | } 83 | 84 | return Ok(()); 85 | } 86 | 87 | persist_process(&paths, cli.daemonize)?; 88 | 89 | let poll = Poll::new().map_err(|err| OhNo::PollError { err })?; 90 | 91 | // whatever we do, we will need to know about where the resources are; this 92 | // object ensures the resources are created and expose interfaces to them 93 | let registry = Arc::new( 94 | poll 95 | .registry() 96 | .try_clone() 97 | .map_err(|err| OhNo::PollError { err })?, 98 | ); 99 | let resources = ServerResources::new(paths, registry)?; 100 | let mut server = Server::new(&config, &cli, resources, poll)?; 101 | 102 | if let Some(session) = cli.init { 103 | server.init_first_session(session)?; 104 | } 105 | 106 | return server.start(); 107 | } 108 | 109 | Err(OhNo::NothingToDo) 110 | } 111 | 112 | /// Create the PID file from the current process, or the one of the child 113 | /// process if daemonized. 114 | fn persist_process(paths: &Paths, daemonize: bool) -> Result<(), OhNo> { 115 | let pid_file = paths.pid_path(); 116 | 117 | // check whether a pid file exists; remove it if any 118 | if let Ok(true) = pid_file.try_exists() { 119 | log::debug!("removing previous PID file"); 120 | std::fs::remove_file(&pid_file).map_err(|err| OhNo::CannotStartDaemon { 121 | err: format!( 122 | "cannot remove previous PID file {path}: {err}", 123 | path = pid_file.display() 124 | ), 125 | })?; 126 | 127 | log::debug!("removing previous socket file"); 128 | let socket_path = paths.socket_path(); 129 | 130 | if let Ok(true) = socket_path.try_exists() { 131 | if let Err(err) = std::fs::remove_file(&socket_path) { 132 | return Err(OhNo::CannotStartDaemon { 133 | err: format!( 134 | "cannot remove previous socket file {path}: {err}", 135 | path = socket_path.display() 136 | ), 137 | }); 138 | } 139 | } 140 | } 141 | 142 | if daemonize { 143 | // create stdout / stderr files 144 | let stdout_path = paths.stdout(); 145 | let stderr_path = paths.stderr(); 146 | let stdout = File::create(&stdout_path).map_err(|err| OhNo::CannotCreateFile { 147 | file: stdout_path, 148 | err, 149 | })?; 150 | let stderr = File::create(&stderr_path).map_err(|err| OhNo::CannotCreateFile { 151 | file: stderr_path, 152 | err, 153 | })?; 154 | 155 | daemonize::Daemonize::new() 156 | .stdout(stdout) 157 | .stderr(stderr) 158 | .pid_file(pid_file) 159 | .start() 160 | .map_err(|err| OhNo::CannotStartDaemon { 161 | err: err.to_string(), 162 | })?; 163 | } else { 164 | std::fs::write(&pid_file, format!("{}", std::process::id())).map_err(|err| { 165 | OhNo::CannotWriteFile { 166 | file: pid_file, 167 | err, 168 | } 169 | })?; 170 | } 171 | 172 | Ok(()) 173 | } 174 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/protocol.rs: -------------------------------------------------------------------------------- 1 | //! Protocol used between servers and clients. 2 | //! 3 | //! The protocol basically explains how data flows from Kakoune to KTS and 4 | //! vice-versa. 5 | 6 | pub mod request; 7 | pub mod response; 8 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/protocol/request.rs: -------------------------------------------------------------------------------- 1 | //! Requests that can be sent to the server from Kakoune. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{error::OhNo, kakoune::text_objects::OperationMode, tree_sitter::nav}; 6 | 7 | use super::response::{self, Response}; 8 | 9 | /// Request. 10 | #[derive(Debug, Deserialize, Serialize)] 11 | #[serde(tag = "type", rename_all = "snake_case")] 12 | pub struct Request { 13 | session: String, 14 | client: Option, 15 | buffer: Option, 16 | payload: Payload, 17 | } 18 | 19 | impl Request { 20 | /// Parse a [`Request`] from a JSON string. 21 | pub fn from_json(s: impl AsRef) -> Result { 22 | let s = s.as_ref(); 23 | serde_json::from_str(s).map_err(|err| OhNo::InvalidRequest { 24 | req: s.to_owned(), 25 | err: err.to_string(), 26 | }) 27 | } 28 | 29 | pub fn init_session(session: impl Into) -> Self { 30 | Self { 31 | session: session.into(), 32 | client: None, 33 | buffer: None, 34 | payload: Payload::SessionBegin, 35 | } 36 | } 37 | 38 | pub fn session(&self) -> &str { 39 | &self.session 40 | } 41 | 42 | pub fn buffer(&self) -> Option<&str> { 43 | self.buffer.as_deref() 44 | } 45 | 46 | pub fn payload(&self) -> &Payload { 47 | &self.payload 48 | } 49 | 50 | pub fn reply(&self, payload: response::Payload) -> Response { 51 | Response::new( 52 | self.session.clone(), 53 | self.client.clone(), 54 | self.buffer.clone(), 55 | payload, 56 | ) 57 | } 58 | } 59 | 60 | /// Request payload. 61 | #[derive(Debug, Deserialize, Serialize)] 62 | #[serde(tag = "type", rename_all = "snake_case")] 63 | pub enum Payload { 64 | /// Inform the server that a session exists and that we should be sending back 65 | /// the Kakoune commands to get the server features. 66 | SessionBegin, 67 | 68 | /// Inform the server that a session has exited. 69 | SessionEnd, 70 | 71 | /// Ask the server to reload its configuration and reload grammars / queries. 72 | Reload, 73 | 74 | /// Ask the server to shutdown. 75 | Shutdown, 76 | 77 | /// Buffer metadata. 78 | /// 79 | /// This should be sent every time the buffer changes (lang, mostly). 80 | BufferMetadata { lang: String }, 81 | 82 | /// Buffer close. 83 | BufferClose, 84 | 85 | /// Request to apply text-objects on selections. 86 | TextObjects { 87 | buffer: String, 88 | pattern: String, 89 | selections: String, 90 | mode: OperationMode, 91 | }, 92 | 93 | /// Request to navigate the tree-sitter tree on selections. 94 | Nav { 95 | buffer: String, 96 | selections: String, 97 | dir: nav::Dir, 98 | }, 99 | } 100 | 101 | /// Possible way of updating a buffer. 102 | #[derive(Debug, Deserialize, Serialize)] 103 | #[serde(tag = "type", rename_all = "snake_case")] 104 | pub enum BufferUpdate { 105 | /// The full buffer is sent over the buffer FIFO. 106 | Full, 107 | } 108 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/protocol/response.rs: -------------------------------------------------------------------------------- 1 | //! Response sent from the daemon to Kakoune. 2 | 3 | use std::{path::PathBuf, sync::mpsc::Sender}; 4 | 5 | use itertools::Itertools; 6 | 7 | use crate::{kakoune::selection::Sel, tree_sitter::highlighting::KakHighlightRange}; 8 | 9 | /// Response sent from KTS to Kakoune. 10 | #[derive(Debug, Eq, PartialEq)] 11 | pub struct Response { 12 | session: String, 13 | client: Option, 14 | buffer: Option, 15 | payload: Payload, 16 | } 17 | 18 | impl Response { 19 | pub fn new( 20 | session: impl Into, 21 | client: impl Into>, 22 | buffer: impl Into>, 23 | payload: Payload, 24 | ) -> Self { 25 | Self { 26 | session: session.into(), 27 | client: client.into(), 28 | buffer: buffer.into(), 29 | payload, 30 | } 31 | } 32 | 33 | pub fn session(&self) -> &str { 34 | &self.session 35 | } 36 | 37 | pub fn to_kak(&self) -> Option { 38 | let payload = self.payload.to_kak(); 39 | 40 | // empty payload means no response 41 | if payload.is_empty() { 42 | return None; 43 | } 44 | 45 | let prefix = if let Some(ref buffer) = self.buffer { 46 | format!("-buffer '{buffer}' ") 47 | } else if let Some(ref client) = self.client { 48 | format!("-try-client '{client}' ") 49 | } else { 50 | String::new() 51 | }; 52 | 53 | Some(format!( 54 | "evaluate-commands -no-hooks {prefix} -- %[ {payload} ]" 55 | )) 56 | } 57 | } 58 | 59 | /// Response payload. 60 | #[derive(Debug, Eq, PartialEq)] 61 | pub enum Payload { 62 | /// Initial response when a session starts. 63 | /// 64 | /// This is a list of (language, remove_default_highlighter) configuration. 65 | Init { enabled_langs: Vec<(String, bool)> }, 66 | 67 | /// Explicit deinit response when the daemon exits. 68 | /// 69 | /// This is sent to all connected sessions to ask them to deinit when the server is going down. This is important as 70 | /// a KTS-enabled session will use various resources (UNIX sockets, FIFOs, etc.) to communicate with KTS, and most of 71 | /// those will block on Kakoune. 72 | Deinit, 73 | 74 | /// A buffer metadata changes and the new version is accepted by the server. 75 | BufferSetup { 76 | /// FIFO where Kakoune should stream update 77 | fifo_path: PathBuf, 78 | 79 | /// Sentinel code used to delimit end of buffers inside the FIFO. 80 | sentinel: String, 81 | }, 82 | 83 | /// Highlights. 84 | /// 85 | /// This response is generated when new highlights are available. 86 | Highlights { ranges: Vec }, 87 | 88 | /// Selections. 89 | /// 90 | /// These selections are typically returned when the user asked to perform text-objects queries. 91 | Selections { sels: Vec }, 92 | } 93 | 94 | impl Payload { 95 | /// Turn the [`Payload`] into a Kakoune command that can be executed remotely. 96 | pub fn to_kak(&self) -> String { 97 | match self { 98 | Payload::Init { enabled_langs } => { 99 | let add_hl = 100 | "add-highlighter -override buffer/tree-sitter-highlighter ranges tree_sitter_hl_ranges"; 101 | let per_lang = enabled_langs 102 | .iter() 103 | .map(|(lang, remove_default_highlighter)| { 104 | let remove_default_hl = if *remove_default_highlighter { 105 | format!("try 'remove-highlighter window/{lang}'") 106 | } else { 107 | String::new() 108 | }; 109 | 110 | let mut config = format!( 111 | "hook -group tree-sitter global WinSetOption tree_sitter_lang={lang} %< 112 | {remove_default_hl} 113 | tree-sitter-buffer-metadata 114 | {add_hl} 115 | tree-sitter-user-after-highlighter 116 | >", 117 | ); 118 | 119 | if *remove_default_highlighter { 120 | config.push_str(&format!("\ntry 'remove-hooks global {lang}-highlight'")); 121 | } 122 | 123 | config 124 | }) 125 | .join("\n"); 126 | 127 | [ 128 | per_lang, 129 | "tree-sitter-hook-install-session".to_owned(), 130 | "tree-sitter-initial-set-buffer-lang".to_owned(), 131 | ] 132 | .join("\n") 133 | } 134 | 135 | Payload::Deinit => "tree-sitter-remove-all".to_owned(), 136 | 137 | Payload::BufferSetup { 138 | fifo_path, 139 | sentinel, 140 | } => [ 141 | format!( 142 | "set-option buffer tree_sitter_buf_fifo_path {}", 143 | fifo_path.display() 144 | ), 145 | format!("set-option buffer tree_sitter_buf_sentinel {sentinel}"), 146 | "tree-sitter-hook-install-update".to_owned(), 147 | ] 148 | .into_iter() 149 | .filter(|s| !s.is_empty()) 150 | .join("\n"), 151 | 152 | Payload::Highlights { ranges } => { 153 | let ranges_str = ranges 154 | .iter() 155 | .map(KakHighlightRange::to_kak_range_str) 156 | .join(" "); 157 | 158 | format!( 159 | "{range_specs} %val{{timestamp}} {ranges_str}", 160 | range_specs = "set buffer tree_sitter_hl_ranges", 161 | ) 162 | } 163 | 164 | Payload::Selections { sels } => { 165 | let sels_str = sels.iter().map(|sel| sel.to_kak_str()).join(" "); 166 | format!("select {sels_str}") 167 | } 168 | } 169 | } 170 | } 171 | 172 | /// Add replies to the response queue. 173 | /// 174 | /// Response are not immediately sent back to Kakoune, but instead enqueued into 175 | /// the response queue. That is required so that we do not block while sending. 176 | #[derive(Clone, Debug)] 177 | pub struct EnqueueResponse { 178 | sender: Sender, 179 | } 180 | 181 | impl EnqueueResponse { 182 | /// Create a new [`ResponseSender`]. 183 | pub fn new(sender: Sender) -> Self { 184 | Self { sender } 185 | } 186 | 187 | /// Enqueue a response. 188 | /// 189 | /// That function never fails as it logs any underlying errors. 190 | pub fn enqueue(&self, resp: Response) { 191 | if let Err(err) = self.sender.send(resp) { 192 | log::error!("cannot send response: {err}"); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/server/fifo.rs: -------------------------------------------------------------------------------- 1 | //! A simple FIFO wrapper. 2 | 3 | use std::{ 4 | ffi::CString, 5 | fs::{self, File, OpenOptions}, 6 | io::{ErrorKind, Read}, 7 | os::{ 8 | fd::AsRawFd, 9 | unix::{ffi::OsStrExt, fs::OpenOptionsExt}, 10 | }, 11 | path::{Path, PathBuf}, 12 | sync::{Arc, Mutex}, 13 | }; 14 | 15 | use mio::{unix::SourceFd, Interest, Registry, Token}; 16 | 17 | use crate::error::OhNo; 18 | 19 | use super::tokens::Tokens; 20 | 21 | #[derive(Debug)] 22 | pub struct Fifo { 23 | registry: Arc, 24 | tokens: Arc>, 25 | path: PathBuf, 26 | file: File, 27 | tkn: Token, 28 | sentinel: String, 29 | buf: String, 30 | } 31 | 32 | impl Fifo { 33 | /// Create a FIFO. 34 | pub fn create( 35 | registry: &Arc, 36 | tokens: &Arc>, 37 | path: impl Into, 38 | ) -> Result { 39 | let path = path.into(); 40 | 41 | Self::create_fifo(&path)?; 42 | let file = Self::open_nonblocking(&path)?; 43 | let tkn = Self::register(registry, tokens, &file)?; 44 | let sentinel = uuid::Uuid::new_v4().to_string(); 45 | 46 | Ok(Self { 47 | registry: registry.clone(), 48 | tokens: tokens.clone(), 49 | path, 50 | file, 51 | tkn, 52 | sentinel, 53 | buf: String::default(), 54 | }) 55 | } 56 | 57 | /// Wrap libc::mkfifo() and create the FIFO on the filesystem within the [`ServerResources`]. 58 | fn create_fifo(path: &Path) -> Result<(), OhNo> { 59 | // if the file already exists, abort 60 | if let Ok(true) = path.try_exists() { 61 | log::debug!("FIFO already exists for path {}", path.display()); 62 | return Ok(()); 63 | } 64 | 65 | let path_bytes = path.as_os_str().as_bytes(); 66 | let c_path = CString::new(path_bytes).map_err(|err| OhNo::CannotCreateFifo { 67 | err: err.to_string(), 68 | })?; 69 | 70 | let c_err = unsafe { libc::mkfifo(c_path.as_ptr(), 0o644) }; 71 | if c_err != 0 { 72 | return Err(OhNo::CannotCreateFifo { 73 | err: format!("cannot create FIFO at path {path}", path = path.display()), 74 | }); 75 | } 76 | 77 | Ok(()) 78 | } 79 | 80 | fn open_nonblocking(path: &Path) -> Result { 81 | OpenOptions::new() 82 | .read(true) 83 | .custom_flags(libc::O_NONBLOCK) 84 | .open(path) 85 | .map_err(|err| OhNo::CannotOpenFifo { err }) 86 | } 87 | 88 | fn register( 89 | registry: &Arc, 90 | tokens: &Arc>, 91 | file: &File, 92 | ) -> Result { 93 | let tkn = tokens.lock().expect("tokens").create(); 94 | registry 95 | .register(&mut SourceFd(&file.as_raw_fd()), tkn, Interest::READABLE) 96 | .map_err(|err| OhNo::PollError { err })?; 97 | 98 | Ok(tkn) 99 | } 100 | 101 | fn unregister(&self) { 102 | if let Err(err) = self 103 | .registry 104 | .deregister(&mut SourceFd(&self.file.as_raw_fd())) 105 | { 106 | log::error!( 107 | "cannot unregister FIFO {path} from poll registry: {err}", 108 | path = self.path.display() 109 | ); 110 | } 111 | 112 | self.tokens.lock().expect("tokens").recycle(self.tkn); 113 | } 114 | 115 | pub fn token(&self) -> &Token { 116 | &self.tkn 117 | } 118 | 119 | pub fn path(&self) -> &Path { 120 | &self.path 121 | } 122 | 123 | pub fn sentinel(&self) -> &str { 124 | &self.sentinel 125 | } 126 | 127 | pub fn read_to_buf(&mut self, target: &mut String) -> Result { 128 | loop { 129 | match self.file.read_to_string(&mut self.buf) { 130 | Ok(0) => break, 131 | Ok(_) => continue, 132 | 133 | Err(err) => match err.kind() { 134 | ErrorKind::WouldBlock => break, 135 | 136 | _ => { 137 | // reset the buffer in case of errors 138 | self.buf.clear(); 139 | return Err(OhNo::CannotReadFifo { err }); 140 | } 141 | }, 142 | } 143 | } 144 | 145 | // search for the sentinel; if we find it, it means we have a complete 146 | // buffer; cut it from the data and reset to be ready to read the next 147 | // buffer 148 | if let Some(index) = self.buf.find(&self.sentinel) { 149 | log::trace!( 150 | "found sentinel {sentinel} in buffer {path}", 151 | sentinel = self.sentinel, 152 | path = self.path.display() 153 | ); 154 | target.clear(); 155 | target.push_str(&self.buf[..index]); 156 | 157 | log::trace!("new buffer content:\n{target}"); 158 | 159 | self.buf.drain(..index + self.sentinel.len()); 160 | return Ok(true); 161 | } 162 | 163 | Ok(false) 164 | } 165 | } 166 | 167 | // We implement Drop here because we want to clean the FIFO when the session 168 | // exits automatically. Failing doing so is not a hard error. 169 | impl Drop for Fifo { 170 | fn drop(&mut self) { 171 | self.unregister(); 172 | 173 | if let Err(err) = fs::remove_file(&self.path) { 174 | log::warn!("cannot remove FIFO at path {}: {err}", self.path.display()); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/server/handler.rs: -------------------------------------------------------------------------------- 1 | use kak_tree_sitter_config::Config; 2 | use mio::Token; 3 | 4 | use crate::{ 5 | error::OhNo, 6 | kakoune::{buffer::BufferId, selection::Sel, text_objects::OperationMode}, 7 | protocol::response::{Payload, Response}, 8 | tree_sitter::{languages::Languages, nav, state::Trees}, 9 | }; 10 | 11 | use super::resources::ServerResources; 12 | 13 | /// Type responsible for handling tree-sitter requests. 14 | /// 15 | /// This type is stateful, as requests might have side-effect (i.e. tree-sitter 16 | /// parsing generates trees/highlighters that can be reused, for instance). 17 | pub struct Handler { 18 | trees: Trees, 19 | langs: Languages, 20 | with_highlighting: bool, 21 | } 22 | 23 | impl Handler { 24 | pub fn new(config: &Config, with_highlighting: bool) -> Result { 25 | let trees = Trees::default(); 26 | let langs = Languages::load_from_dir(config)?; 27 | 28 | Ok(Self { 29 | trees, 30 | langs, 31 | with_highlighting, 32 | }) 33 | } 34 | 35 | /// Initiate languages on session init. 36 | pub fn handle_session_begin(&mut self) -> Payload { 37 | let enabled_langs = self 38 | .langs 39 | .langs() 40 | .map(|(name, lang)| (name.to_owned(), lang.remove_default_highlighter)) 41 | .collect(); 42 | Payload::Init { enabled_langs } 43 | } 44 | 45 | /// Update buffer metadata change. 46 | pub fn handle_buffer_metadata( 47 | &mut self, 48 | resources: &mut ServerResources, 49 | id: &BufferId, 50 | lang: &str, 51 | ) -> Result { 52 | let lang = self.langs.get(lang)?; 53 | let tree = self.trees.compute(resources, lang, id)?; 54 | let fifo = tree.fifo(); 55 | let fifo_path = fifo.path().to_owned(); 56 | let sentinel = fifo.sentinel().to_owned(); 57 | 58 | Ok(Payload::BufferSetup { 59 | fifo_path, 60 | sentinel, 61 | }) 62 | } 63 | 64 | /// Handle buffer close. 65 | pub fn handle_buffer_close(&mut self, id: &BufferId) { 66 | self.trees.delete_tree(id); 67 | } 68 | 69 | /// Update a full buffer update. 70 | pub fn handle_full_buffer_update(&mut self, tkn: Token) -> Result, OhNo> { 71 | let id = self.trees.get_buf_id(&tkn)?.clone(); 72 | log::debug!("updating {id:?}, token {tkn:?}"); 73 | let tree = self.trees.get_tree_mut(&id)?; 74 | 75 | // update the tree 76 | if !tree.update_buf()? { 77 | // early return if no update occurred 78 | return Ok(None); 79 | } 80 | 81 | // run any additional post-processing on the buffer 82 | if !self.with_highlighting { 83 | return Ok(None); 84 | } 85 | 86 | // serve highlight 87 | let lang = self.langs.get(tree.lang())?; 88 | let ranges = tree.highlight(lang, |inject_lang| { 89 | self 90 | .langs 91 | .get(inject_lang) 92 | .ok() 93 | .map(|lang2| &lang2.hl_config) 94 | })?; 95 | 96 | let resp = Response::new( 97 | id.session(), 98 | None, 99 | id.buffer().to_owned(), 100 | Payload::Highlights { ranges }, 101 | ); 102 | 103 | Ok(Some(resp)) 104 | } 105 | 106 | pub fn handle_text_objects( 107 | &mut self, 108 | id: &BufferId, 109 | pattern: &str, 110 | selections: &[Sel], 111 | mode: &OperationMode, 112 | ) -> Result { 113 | log::debug!("text-objects {pattern} for buffer {id:?}"); 114 | 115 | let tree_state = self.trees.get_tree(id)?; 116 | let lang = self.langs.get(tree_state.lang())?; 117 | let sels = tree_state.text_objects(lang, pattern, selections, mode)?; 118 | 119 | Ok(Payload::Selections { sels }) 120 | } 121 | 122 | pub fn handle_nav( 123 | &mut self, 124 | id: &BufferId, 125 | selections: &[Sel], 126 | dir: nav::Dir, 127 | ) -> Result { 128 | log::debug!("nav {dir:?} for buffer {id:?}"); 129 | 130 | let tree_state = self.trees.get_tree(id)?; 131 | let sels = tree_state.nav_tree(selections, dir); 132 | 133 | Ok(Payload::Selections { sels }) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/server/resources.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | path::PathBuf, 4 | process::Command, 5 | sync::{Arc, Mutex}, 6 | }; 7 | 8 | use mio::Registry; 9 | 10 | use crate::error::OhNo; 11 | 12 | use super::{fifo::Fifo, tokens::Tokens}; 13 | 14 | /// Paths used by all KTS components. 15 | #[derive(Debug)] 16 | pub struct Paths { 17 | runtime_dir: PathBuf, 18 | } 19 | 20 | impl Paths { 21 | pub fn new() -> Result { 22 | let dir = dirs::runtime_dir() 23 | .or_else(|| 24 | // macOS doesn’t implement XDG, yay… 25 | std::env::var("TMPDIR").map(PathBuf::from).ok()) 26 | .ok_or_else(|| OhNo::NoRuntimeDir)?; 27 | let runtime_dir = dir.join("kak-tree-sitter"); 28 | 29 | // create the runtime dir if it doesn’t exist 30 | if let Ok(false) = runtime_dir.try_exists() { 31 | fs::create_dir(&runtime_dir).map_err(|err| OhNo::CannotCreateDir { 32 | dir: runtime_dir.clone(), 33 | err, 34 | })?; 35 | } 36 | 37 | Ok(Paths { runtime_dir }) 38 | } 39 | 40 | /// Socket used by the server to receive initial commands. 41 | pub fn socket_path(&self) -> PathBuf { 42 | self.runtime_dir.join("socket") 43 | } 44 | 45 | pub fn pid_path(&self) -> PathBuf { 46 | self.runtime_dir.join("pid") 47 | } 48 | 49 | pub fn stdout(&self) -> PathBuf { 50 | self.runtime_dir.join("stdout.txt") 51 | } 52 | 53 | pub fn stderr(&self) -> PathBuf { 54 | self.runtime_dir.join("stderr.txt") 55 | } 56 | 57 | pub fn bufs_dir(&self) -> PathBuf { 58 | self.runtime_dir.join("bufs") 59 | } 60 | } 61 | 62 | /// Resources requiring a special drop implementation. 63 | #[derive(Debug)] 64 | pub struct ServerResources { 65 | paths: Paths, 66 | tokens: Arc>, 67 | registry: Arc, 68 | } 69 | 70 | impl ServerResources { 71 | pub fn new(paths: Paths, registry: Arc) -> Result { 72 | log::debug!("initializing server resources"); 73 | let tokens = Arc::new(Mutex::new(Tokens::default())); 74 | 75 | // create the resources 76 | let sr = Self { 77 | paths, 78 | tokens, 79 | registry, 80 | }; 81 | sr.io_create()?; 82 | 83 | Ok(sr) 84 | } 85 | 86 | pub fn is_running(pid: &str) -> bool { 87 | log::debug!("checking whether kak-tree-sitter ({pid}) is still running"); 88 | Command::new("ps") 89 | .args(["-p", pid]) 90 | .output() 91 | .is_ok_and(|o| o.status.success()) 92 | } 93 | 94 | /// Create resources, if not already there. 95 | fn io_create(&self) -> Result<(), OhNo> { 96 | log::debug!("ensuring that runtime directories exist (creating if not)"); 97 | let buffers_dir = self.paths.bufs_dir(); 98 | fs::create_dir_all(&buffers_dir).map_err(|err| OhNo::CannotCreateDir { 99 | dir: buffers_dir, 100 | err, 101 | })?; 102 | 103 | Ok(()) 104 | } 105 | 106 | pub fn paths(&self) -> &Paths { 107 | &self.paths 108 | } 109 | 110 | pub fn tokens(&self) -> &Arc> { 111 | &self.tokens 112 | } 113 | 114 | /// Generate a new, unique FIFO path. 115 | fn new_fifo_path(&self) -> Result { 116 | let name = uuid::Uuid::new_v4(); 117 | Ok(self.paths.bufs_dir().join(name.to_string())) 118 | } 119 | 120 | /// Create a new FIFO and associate it with a token on the given poll. 121 | pub fn new_fifo(&mut self) -> Result { 122 | let path = self.new_fifo_path()?; 123 | Fifo::create(&self.registry, &self.tokens, path) 124 | } 125 | } 126 | 127 | impl Drop for ServerResources { 128 | fn drop(&mut self) { 129 | let _ = std::fs::remove_dir_all(self.paths.pid_path()); 130 | let _ = std::fs::remove_dir_all(self.paths.socket_path()); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/server/tokens.rs: -------------------------------------------------------------------------------- 1 | use mio::Token; 2 | 3 | /// Distribute tokens. 4 | /// 5 | /// Tokens are provided by first trying to give recyled ones. If none is 6 | /// remaining, new tokens are provided. 7 | #[derive(Debug)] 8 | pub struct Tokens { 9 | next: Token, 10 | free_list: Vec, 11 | } 12 | 13 | impl Default for Tokens { 14 | fn default() -> Self { 15 | Self { 16 | // 0 is for wake; 1 is for the UNIX listener; 2+ is for the rest 17 | next: Token(2), 18 | free_list: Vec::default(), 19 | } 20 | } 21 | } 22 | 23 | impl Tokens { 24 | pub fn create(&mut self) -> Token { 25 | self.free_list.pop().unwrap_or_else(|| { 26 | let t = self.next; 27 | self.next = Token(t.0 + 1); 28 | t 29 | }) 30 | } 31 | 32 | pub fn recycle(&mut self, tkn: Token) { 33 | self.free_list.push(tkn); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/tree_sitter.rs: -------------------------------------------------------------------------------- 1 | pub mod highlighting; 2 | pub mod languages; 3 | pub mod nav; 4 | pub mod queries; 5 | pub mod state; 6 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/tree_sitter/languages.rs: -------------------------------------------------------------------------------- 1 | //! Supported languages. 2 | //! 3 | //! Languages have different objects (grammars, queries, etc.) living at runtime and must be loaded beforehand. 4 | 5 | use std::{collections::HashMap, path::Path}; 6 | 7 | use kak_tree_sitter_config::{Config, LanguagesConfig}; 8 | use libloading::Symbol; 9 | use tree_sitter::Query; 10 | use tree_sitter_highlight::HighlightConfiguration; 11 | 12 | use crate::{error::OhNo, tree_sitter::queries::Queries}; 13 | 14 | pub struct Language { 15 | pub name: String, 16 | pub hl_config: HighlightConfiguration, 17 | pub hl_names: Vec, 18 | // whether we should remove the default highlighter when highlighting a buffer with this language 19 | pub remove_default_highlighter: bool, 20 | // query to use for text objects, if supported by the language 21 | pub textobject_query: Option, 22 | 23 | // NOTE: we need to keep that alive *probably*; better be safe than sorry 24 | ts_lang: tree_sitter::Language, 25 | _ts_lib: libloading::Library, 26 | } 27 | 28 | impl Language { 29 | pub fn lang_name(&self) -> &str { 30 | &self.name 31 | } 32 | 33 | pub fn lang(&self) -> tree_sitter::Language { 34 | self.ts_lang 35 | } 36 | } 37 | 38 | pub struct Languages { 39 | /// Map a `kts_lang` to the tree-sitter [`Language`] and its queries. 40 | langs: HashMap, 41 | } 42 | 43 | impl Languages { 44 | /// Load a grammar. 45 | fn load_grammar( 46 | lang: &str, 47 | path: &Path, 48 | ) -> Result<(libloading::Library, tree_sitter::Language), OhNo> { 49 | let lib = unsafe { libloading::Library::new(path) }; 50 | let lib = lib.map_err(|err| OhNo::CannotLoadGrammar { 51 | lang: lang.to_owned(), 52 | err: err.to_string(), 53 | })?; 54 | let fn_sym = format!("tree_sitter_{}", lang.replace(['.', '-'], "_")); 55 | 56 | let sym: Result tree_sitter::Language>, _> = 57 | unsafe { lib.get(fn_sym.as_bytes()) }; 58 | let sym = sym.map_err(|err| OhNo::CannotLoadGrammar { 59 | lang: lang.to_owned(), 60 | err: format!("cannot find language: {err}"), 61 | })?; 62 | let sym = sym(); 63 | 64 | Ok((lib, sym)) 65 | } 66 | 67 | /// Load languages. 68 | /// 69 | /// This function will scan the directory and extract / map all the languages. 70 | pub fn load_from_dir(config: &Config) -> Result { 71 | let mut langs = HashMap::new(); 72 | 73 | // iterate over all known languages in the configuration 74 | for (lang_name, lang_config) in &config.languages.language { 75 | log::info!("loading configuration for {lang_name}"); 76 | 77 | if let Some(grammar_path) = LanguagesConfig::get_grammar_path(lang_config, lang_name) { 78 | log::debug!(" grammar path: {}", grammar_path.display()); 79 | 80 | let (ts_lib, ts_lang) = match Self::load_grammar(lang_name, &grammar_path) { 81 | Ok(x) => x, 82 | Err(err) => { 83 | log::warn!("{err}"); 84 | continue; 85 | } 86 | }; 87 | 88 | if let Some(queries_dir) = LanguagesConfig::get_queries_dir(lang_config, lang_name) { 89 | log::debug!(" queries directory: {}", queries_dir.display()); 90 | 91 | let queries = Queries::load_from_dir(queries_dir); 92 | let mut hl_config = match HighlightConfiguration::new( 93 | ts_lang, 94 | queries.highlights.as_deref().unwrap_or(""), 95 | queries.injections.as_deref().unwrap_or(""), 96 | queries.locals.as_deref().unwrap_or(""), 97 | ) { 98 | Ok(x) => x, 99 | Err(err) => { 100 | log::error!("failed to load highlighter for {lang_name}: {err}"); 101 | continue; 102 | } 103 | }; 104 | 105 | let hl_names: Vec<_> = config.highlight.groups.iter().cloned().collect(); 106 | hl_config.configure(&hl_names); 107 | 108 | let remove_default_highlighter = lang_config.remove_default_highlighter.into(); 109 | 110 | let textobject_query = queries 111 | .text_objects 112 | .as_deref() 113 | .map(|q| Query::new(ts_lang, q).map(Some)) 114 | .unwrap_or_else(|| Ok(None))?; 115 | 116 | let lang = Language { 117 | name: lang_name.clone(), 118 | hl_config, 119 | hl_names, 120 | remove_default_highlighter, 121 | textobject_query, 122 | ts_lang, 123 | _ts_lib: ts_lib, 124 | }; 125 | langs.insert(lang_name.to_owned(), lang); 126 | } 127 | } 128 | } 129 | 130 | Ok(Self { langs }) 131 | } 132 | 133 | pub fn get(&self, lang: impl AsRef) -> Result<&Language, OhNo> { 134 | let lang = lang.as_ref(); 135 | self.langs.get(lang).ok_or_else(|| OhNo::UnknownLang { 136 | lang: lang.to_owned(), 137 | }) 138 | } 139 | 140 | pub fn langs(&self) -> impl Iterator { 141 | self.langs.iter().map(|(name, lang)| (name.as_str(), lang)) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/tree_sitter/nav.rs: -------------------------------------------------------------------------------- 1 | //! Tree-sitter navigation. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Every possible navigation directions. 6 | /// 7 | /// Navigation within a tree-sitter tree is provided via several directions. 8 | #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] 9 | #[serde(rename_all = "snake_case")] 10 | pub enum Dir { 11 | /// Parent node. 12 | Parent, 13 | 14 | /// First child of the current node, if any. 15 | FirstChild, 16 | 17 | // Last child of the current node, if any. 18 | LastChild, 19 | 20 | /// First sibling of the current node, if any. 21 | FirstSibling, 22 | 23 | /// Last sibling of the current node if any. 24 | LastSibling, 25 | 26 | /// Previous sibiling of the current node, if any. 27 | PrevSibling { 28 | /// Should we take cousins into account? 29 | #[serde(default)] 30 | cousin: bool, 31 | }, 32 | 33 | /// Next sibling of the current node, if any. 34 | NextSibling { 35 | /// Should we take cousins into account? 36 | cousin: bool, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /kak-tree-sitter/src/tree_sitter/queries.rs: -------------------------------------------------------------------------------- 1 | //! Supported queries. 2 | 3 | use std::{fs, path::Path}; 4 | 5 | #[derive(Debug)] 6 | pub struct Queries { 7 | pub highlights: Option, 8 | pub injections: Option, 9 | pub locals: Option, 10 | pub text_objects: Option, 11 | } 12 | 13 | impl Queries { 14 | pub fn load_from_dir(dir: impl AsRef) -> Self { 15 | let dir = dir.as_ref(); 16 | 17 | let highlights = fs::read_to_string(dir.join("highlights.scm")).ok(); 18 | let injections = fs::read_to_string(dir.join("injections.scm")).ok(); 19 | let locals = fs::read_to_string(dir.join("locals.scm")).ok(); 20 | let text_objects = fs::read_to_string(dir.join("textobjects.scm")).ok(); 21 | 22 | Queries { 23 | highlights, 24 | injections, 25 | locals, 26 | text_objects, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ktsctl/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.1.1 2 | 3 | ## Bug fixes 4 | 5 | - Fix build.rs files to allow for missing SHA1. [49951d1](https://github.com/hadronized/kak-tree-sitter/commit/49951d1) 6 | 7 | # v1.1.0 8 | 9 | ## Features 10 | 11 | - Add `--prune` to `ktsctl rm`. [56e11cc](https://github.com/hadronized/kak-tree-sitter/commit/56e11cc) 12 | - Add support for `ktsctl rm`. [e115b56](https://github.com/hadronized/kak-tree-sitter/commit/e115b56) 13 | 14 | ## Bug fixes 15 | 16 | # v1.0.0 17 | 18 | - Enhance ktsctl UX by removing `manage` and `-l`. 19 | The `manage` command was replaced with `fetch`, `compile`, `install` and 20 | `sync`. 21 | 22 | Additionally, for all commands, the `-l` flag was removed, so that instead of 23 | doing `ktsctl query -l rust`, we now do `ktsctl query rust`. The `-a` flag 24 | remains the same. For what used to be `ktsctl manage -sl rust`, you now just 25 | do `ktsctl sync rust`. 26 | 27 | The only drawback is `ktsctl manage -fcil rust`. You now need to do 28 | `ktsctl fetch rust`, `ktsctl compile rust` and `ktsctl install rust`; that 29 | was made in a way so that people prefer using `sync` instead. 30 | - Update MSRV and dependencies. 31 | - Move to sr.ht. 32 | 33 | # v0.4.0 34 | 35 | - Update Rust grammar and queries pins. [7b590f4](https://github.com/hadronized/kak-tree-sitter/commit/7b590f4) 36 | - Remove default theme and make cascaded tree-sitter faces. [1406e7d](https://github.com/hadronized/kak-tree-sitter/commit/1406e7d) 37 | - Add more generic faces [d118438](https://github.com/hadronized/kak-tree-sitter/commit/d118438) 38 | - Introduce user-only configuration. [fc7c5c6](https://github.com/hadronized/kak-tree-sitter/commit/fc7c5c6) 39 | - Update default-config.md to use sources. [386c81b](https://github.com/hadronized/kak-tree-sitter/commit/386c81b) 40 | - Introduce ktsctl sources. [e083aad](https://github.com/hadronized/kak-tree-sitter/commit/e083aad) 41 | - Work on a better CLI for ktsctl. [8d8d6de](https://github.com/hadronized/kak-tree-sitter/commit/8d8d6de) 42 | - Update man pages for the new ktsctl. [5eeb2f4](https://github.com/hadronized/kak-tree-sitter/commit/5eeb2f4) 43 | - Enhance ktsctl’s look. [3edd4a5](https://github.com/hadronized/kak-tree-sitter/commit/3edd4a5) 44 | - Rename ktsctl’s --has into --lang. [4367082](https://github.com/hadronized/kak-tree-sitter/commit/4367082) 45 | - Support reading local paths when used as sources for both grammars & queries. [305455b](https://github.com/hadronized/kak-tree-sitter/commit/305455b) 46 | - Add support for cached (git) sources. [03e037c](https://github.com/hadronized/kak-tree-sitter/commit/03e037c) 47 | - Force git sources to have a pin. [4c6d168](https://github.com/hadronized/kak-tree-sitter/commit/4c6d168) 48 | - Support detecting out-of-sync pins and more logs in ktsctl. [4903faf](https://github.com/hadronized/kak-tree-sitter/commit/4903faf) 49 | - Add support for syncing resources. [2c7e87c](https://github.com/hadronized/kak-tree-sitter/commit/2c7e87c) 50 | - Add SCSS config. [2a7dcd1](https://github.com/hadronized/kak-tree-sitter/commit/2a7dcd1) 51 | - Add Vue config. [1c87643](https://github.com/hadronized/kak-tree-sitter/commit/1c87643) 52 | - Add Unison config. [c08bfc3](https://github.com/hadronized/kak-tree-sitter/commit/c08bfc3) 53 | - Add Nix config. [8ce2cec](https://github.com/hadronized/kak-tree-sitter/commit/8ce2cec) 54 | - Add C# config. [665d755](https://github.com/hadronized/kak-tree-sitter/commit/665d755) 55 | - Add Elixir config. [b9f85f5](https://github.com/hadronized/kak-tree-sitter/commit/b9f85f5) 56 | - --depth 1 on git operations to speed things up. [6e375be](https://github.com/hadronized/kak-tree-sitter/commit/6e375be) 57 | - Introduce --all. [c0bd4c8](https://github.com/hadronized/kak-tree-sitter/commit/c0bd4c8) 58 | 59 | --- 60 | 61 | SHA 256 sums 62 | 63 | ``` 64 | 5eabce3a45932207046f356f9763b402a3ee02467244daa5c4d2c8b712e99a3d ktsctl.Linux-x86_64 65 | 70b8fcc5a6db25dfe05451a18a09875724cff2d6c7fabdfd7a078cfa68ea7692 ktsctl.macOS-x86_64 66 | ``` 67 | 68 | # v0.3.3 69 | 70 | - Support for kak-tree-sitter-config-v0.4.0 71 | 72 | # v0.3.2 73 | 74 | - Add --has to ktsctl. [baaf735](https://github.com/hadronized/kak-tree-sitter/commit/baaf735) 75 | 76 | # v0.3.1 77 | 78 | - Enhance CLI of ktsctl. [1b8fbbd](https://github.com/hadronized/kak-tree-sitter/commit/1b8fbbd) 79 | 80 | # v0.3.0 81 | 82 | - `` 83 | 84 | # v0.2.0 85 | 86 | - Proper error handling. 87 | - Remove `--query -q`. It is now inferred from the other arguments. 88 | - The meaning of the various `path` configuration option has changed a bit. We do not magically insert more 89 | indirections from there. For instance, if the query config has a given directory set for `path`, that directory 90 | content will be copied to `$XDG_DATA_DIR/kak-tree-sitter/queries/`. 91 | 92 | 93 | # v0.1.0 94 | 95 | - Initial release. 96 | -------------------------------------------------------------------------------- /ktsctl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ktsctl" 3 | version = "1.1.1-dev" 4 | license = "BSD-3-Clause" 5 | authors = ["Dimitri Sabadie "] 6 | description = "CLI controler of kak-tree-sitter" 7 | keywords = ["tree-sitter", "kakoune"] 8 | categories = ["text-editors"] 9 | homepage = "https://github.com/hadronized/kak-tree-sitter" 10 | repository = "https://github.com/hadronized/kak-tree-sitter" 11 | readme = "README.md" 12 | edition = "2021" 13 | rust-version = "1.70.0" 14 | 15 | [dependencies] 16 | clap = { version = "4.5", features = ["derive"] } 17 | colored = "2.1" 18 | dirs = "5.0" 19 | kak-tree-sitter-config = { version = "2.2.0-dev", path = "../kak-tree-sitter-config" } 20 | log = "0.4" 21 | simple_logger = "5" 22 | thiserror = "1.0" 23 | unicode-segmentation = "1.11" 24 | -------------------------------------------------------------------------------- /ktsctl/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ktsctl/README.md: -------------------------------------------------------------------------------- 1 | # ktsctl, `kak-tree-sitter` CLI controller 2 | 3 | `ktsctl` is, as the name implies, a controller for `kak-tree-sitter`. It’s the tool you should be using to interact 4 | with the data files `kak-tree-sitter` will be using to operate correctly (grammars, queries, etc.). 5 | 6 | - [Special note](#special-note) 7 | - [Features](#features) 8 | 9 | ## Special note 10 | 11 | `ktsctl` is _optional_, it is **not mandatory to use it to use `kak-tree-sitter`**. However, it is highly recommended, 12 | because it will perform boring operations for you automatically, and it comes with good defaults. 13 | 14 | ## Features 15 | 16 | - Automatically fetch online resources. It uses `git clone` (`git` from your system) for that. It currently supports 17 | two types of resources: 18 | - Grammars. 19 | - Queries. 20 | - Compile and link grammars. Requires `cc` to be available on your system. 21 | - Install grammars and queries inside your data directories. 22 | - Share the same configuration file as `kak-tree-sitter`. 23 | -------------------------------------------------------------------------------- /ktsctl/build.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Write, process::Command}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let mut version = env!("CARGO_PKG_VERSION").to_owned(); 5 | if let Some(sha1) = git_sha1() { 6 | write!(&mut version, "-{sha1}")?; 7 | } 8 | 9 | println!("cargo:rustc-env=VERSION={version}"); 10 | 11 | Ok(()) 12 | } 13 | 14 | fn git_sha1() -> Option { 15 | Command::new("git") 16 | .args(["rev-parse", "--short", "HEAD"]) 17 | .output() 18 | .ok() 19 | .filter(|stdout| stdout.status.success()) 20 | .and_then(|stdout| String::from_utf8(stdout.stdout).ok()) 21 | .map(|hash| hash.trim().to_owned()) 22 | } 23 | -------------------------------------------------------------------------------- /ktsctl/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Debug, Parser)] 4 | #[clap( 5 | author = "Dimitri Sabadie ", 6 | name = "ktsctl", 7 | version = env!("VERSION"), 8 | about = "CLI controler for kak-tree-sitter" 9 | )] 10 | pub struct Cli { 11 | #[clap(long)] 12 | pub verbose: bool, 13 | 14 | #[clap(subcommand)] 15 | pub cmd: Cmd, 16 | } 17 | 18 | #[derive(Debug, Subcommand)] 19 | pub enum Cmd { 20 | /// Fetch resources. 21 | Fetch { 22 | /// Execute commands for all known languages. 23 | /// 24 | /// The list of languages can be seen with `ktsctl query -a`. 25 | #[clap(short, long)] 26 | all: bool, 27 | 28 | /// Language to manage. 29 | lang: Option, 30 | }, 31 | 32 | /// Compile resources. 33 | Compile { 34 | /// Execute commands for all known languages. 35 | /// 36 | /// The list of languages can be seen with `ktsctl info --all`. 37 | #[clap(short, long)] 38 | all: bool, 39 | 40 | /// Language to manage. 41 | lang: Option, 42 | }, 43 | 44 | /// Install resources. 45 | Install { 46 | /// Execute commands for all known languages. 47 | /// 48 | /// The list of languages can be seen with `ktsctl query -a`. 49 | #[clap(short, long)] 50 | all: bool, 51 | 52 | /// Language to manage. 53 | lang: Option, 54 | }, 55 | 56 | /// Synchronize resources (implies fetch, compile and install). 57 | /// 58 | /// This command also checks whether pinned version are already there; if so, 59 | /// nothing is performed. 60 | Sync { 61 | /// Execute commands for all known languages. 62 | /// 63 | /// The list of languages can be seen with `ktsctl query -a`. 64 | #[clap(short, long)] 65 | all: bool, 66 | 67 | /// Language to manage. 68 | lang: Option, 69 | }, 70 | 71 | /// Get information on installed resources. 72 | Query { 73 | /// List all known languages and display information about them. 74 | #[clap(short, long)] 75 | all: bool, 76 | 77 | /// Get information about a specific language. 78 | lang: Option, 79 | }, 80 | 81 | /// Remove resources. 82 | /// 83 | /// If no flag is passed, -g and -q are assumed. Passing -p will also prune 84 | /// out-of-sync pins; so to completely remove everything for a given language: 85 | /// 86 | /// ktsctl rm -p 87 | #[clap(aliases = &["rm"])] 88 | Remove { 89 | /// Remove grammar. 90 | #[clap(short, long)] 91 | grammar: bool, 92 | 93 | /// Remove queries. 94 | #[clap(short, long)] 95 | queries: bool, 96 | 97 | /// Prune resources. 98 | /// 99 | /// Pruning resources implies resources deletion for which the pin is out 100 | /// of date. 101 | #[clap(short, long)] 102 | prune: bool, 103 | 104 | /// Remove resources for the specific language. 105 | lang: String, 106 | }, 107 | } 108 | -------------------------------------------------------------------------------- /ktsctl/src/commands.rs: -------------------------------------------------------------------------------- 1 | //! Main commands supported by ktsctl. 2 | 3 | pub mod manage; 4 | pub mod query; 5 | pub mod remove; 6 | -------------------------------------------------------------------------------- /ktsctl/src/commands/remove.rs: -------------------------------------------------------------------------------- 1 | //! Module to remove resources. 2 | 3 | use std::fs; 4 | 5 | use colored::Colorize; 6 | use kak_tree_sitter_config::Config; 7 | 8 | use crate::{ 9 | error::HellNo, 10 | resources::Resources, 11 | ui::{report::Report, status_icon::StatusIcon}, 12 | }; 13 | 14 | /// Delete resources associated with a given language. 15 | pub fn remove( 16 | config: &Config, 17 | resources: &Resources, 18 | grammar: bool, 19 | queries: bool, 20 | prune: bool, 21 | lang: impl AsRef, 22 | ) -> Result<(), HellNo> { 23 | let lang = lang.as_ref(); 24 | let lang_config = config.languages.get_lang_config(lang)?; 25 | let report = Report::new(StatusIcon::Sync, format!("removing resources for {lang}")); 26 | let mut errors = Vec::new(); 27 | 28 | if grammar { 29 | remove_grammar(resources, lang, lang_config, prune, &report, &mut errors); 30 | } 31 | 32 | if queries { 33 | remove_queries(resources, lang, lang_config, prune, &report, &mut errors); 34 | } 35 | 36 | if errors.is_empty() { 37 | report.success(format!("{lang} removed")); 38 | } else { 39 | report.error(format!("cannot remove {lang}")); 40 | 41 | for err in errors { 42 | eprintln!("{}", err.red()); 43 | } 44 | } 45 | 46 | Ok(()) 47 | } 48 | 49 | fn remove_grammar( 50 | resources: &Resources, 51 | lang: &str, 52 | lang_config: &kak_tree_sitter_config::LanguageConfig, 53 | prune: bool, 54 | report: &Report, 55 | errors: &mut Vec, 56 | ) { 57 | if prune { 58 | let dir = resources.grammars_dir(lang); 59 | if let Ok(true) = dir.try_exists() { 60 | report.info(format!("removing {lang} grammar")); 61 | 62 | if let Err(err) = fs::remove_dir_all(dir) { 63 | errors.push(format!("cannot remove {lang} grammar: {err}")); 64 | } 65 | } 66 | } else { 67 | let grammar_path = resources.grammar_path_from_config(lang, lang_config); 68 | 69 | if let Ok(true) = grammar_path.try_exists() { 70 | report.info(format!("removing {lang} grammar")); 71 | 72 | if let Err(err) = fs::remove_file(grammar_path) { 73 | errors.push(format!("cannot remove {lang} grammar: {err}")); 74 | } 75 | } 76 | } 77 | } 78 | 79 | fn remove_queries( 80 | resources: &Resources, 81 | lang: &str, 82 | lang_config: &kak_tree_sitter_config::LanguageConfig, 83 | prune: bool, 84 | report: &Report, 85 | errors: &mut Vec, 86 | ) { 87 | let dir = if prune { 88 | Some(resources.queries_dir(lang)) 89 | } else { 90 | resources.queries_dir_from_config(lang, lang_config) 91 | }; 92 | 93 | if let Some(dir) = dir { 94 | if let Ok(true) = dir.try_exists() { 95 | report.info(format!("removing {lang} queries")); 96 | 97 | if let Err(err) = fs::remove_dir_all(dir) { 98 | errors.push(format!("cannot remove {lang} queries: {err}")); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ktsctl/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::PathBuf}; 2 | 3 | use kak_tree_sitter_config::error::ConfigError; 4 | use thiserror::Error; 5 | 6 | /// Hell no! 7 | #[derive(Debug, Error)] 8 | pub enum HellNo { 9 | #[error("logger failed to initialize: {err}")] 10 | LoggerError { 11 | #[from] 12 | err: log::SetLoggerError, 13 | }, 14 | 15 | #[error("no runtime directory available")] 16 | NoRuntimeDir, 17 | 18 | #[error("no data directory to hold grammars / queries")] 19 | NoDataDir, 20 | 21 | #[error("bad path")] 22 | BadPath, 23 | 24 | #[error("cannot create directory {dir}: {err}")] 25 | CannotCreateDir { dir: PathBuf, err: io::Error }, 26 | 27 | #[error("configuration error: {err}")] 28 | ConfigError { 29 | #[from] 30 | err: ConfigError, 31 | }, 32 | 33 | #[error("{process} failed to run: {err}")] 34 | ProcessRunError { process: String, err: io::Error }, 35 | 36 | #[error("{process} exited with error: {err}")] 37 | ProcessExitedWithError { process: String, err: String }, 38 | 39 | #[error("cannot copy {src} to {dest}: {err}")] 40 | CannotCopyFile { 41 | src: PathBuf, 42 | dest: PathBuf, 43 | err: io::Error, 44 | }, 45 | 46 | #[error("cannot recursively copy from {src} to {dest}: {err}")] 47 | CannotCopyDir { 48 | src: PathBuf, 49 | dest: PathBuf, 50 | err: io::Error, 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /ktsctl/src/fs.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadronized/kak-tree-sitter/8e480e2e37d059a290935a59944539c6e39885d5/ktsctl/src/fs.rs -------------------------------------------------------------------------------- /ktsctl/src/git.rs: -------------------------------------------------------------------------------- 1 | //! Git utilities. 2 | 3 | use std::{fs, path::Path}; 4 | 5 | use crate::{error::HellNo, process::Process, ui::report::Report}; 6 | 7 | /// Result of a successful git clone. 8 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 9 | pub enum Clone { 10 | /// The repository was cloned remotely. 11 | Cloned, 12 | 13 | /// The repository was already cloned and thus a cached version is used. 14 | Cached, 15 | } 16 | 17 | /// Clone a git repository. 18 | /// 19 | /// Return `Ok(true)` if something was cloned; `Ok(false)` if it was already there. 20 | pub fn clone(report: &Report, fetch_path: &Path, url: &str) -> Result { 21 | // check if the fetch path already exists; if not, we clone the repository 22 | let fetched; 23 | if let Ok(false) = fetch_path.try_exists() { 24 | report.fetch(format!("cloning {url}")); 25 | 26 | fs::create_dir_all(fetch_path).map_err(|err| HellNo::CannotCreateDir { 27 | dir: fetch_path.to_owned(), 28 | err, 29 | })?; 30 | 31 | // shallow clone of the repository 32 | let git_clone_args = [ 33 | "clone", 34 | "--depth", 35 | "1", 36 | "-n", 37 | url, 38 | fetch_path 39 | .as_os_str() 40 | .to_str() 41 | .ok_or_else(|| HellNo::BadPath)?, 42 | ]; 43 | 44 | Process::new("git").run(None, &git_clone_args)?; 45 | fetched = Clone::Cloned; 46 | } else { 47 | fetched = Clone::Cached; 48 | } 49 | 50 | Ok(fetched) 51 | } 52 | 53 | /// Checkout a source at a given pin. 54 | pub fn checkout(report: &Report, url: &str, fetch_path: &Path, pin: &str) -> Result<(), HellNo> { 55 | report.info(format!("checking out {url} at {pin}")); 56 | Process::new("git").run(fetch_path, &["checkout", pin]) 57 | } 58 | 59 | /// Fetch remote git objects. 60 | /// 61 | /// This function expects a `pin` to prevent fetching the whole remote repository. 62 | pub fn fetch( 63 | report: &Report, 64 | lang: &str, 65 | fetch_path: &Path, 66 | url: &str, 67 | pin: &str, 68 | ) -> Result<(), HellNo> { 69 | report.sync(format!("fetching {lang} git remote objects {url}")); 70 | Process::new("git").run(fetch_path, &["fetch", "origin", "--prune", pin])?; 71 | checkout(report, url, fetch_path, pin) 72 | } 73 | -------------------------------------------------------------------------------- /ktsctl/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use clap::Parser; 4 | use cli::Cli; 5 | use colored::Colorize; 6 | use error::HellNo; 7 | use kak_tree_sitter_config::Config; 8 | 9 | use crate::{ 10 | commands::{ 11 | manage::{ManageFlags, Manager}, 12 | query::Query, 13 | remove, 14 | }, 15 | resources::Resources, 16 | }; 17 | 18 | mod cli; 19 | mod commands; 20 | mod error; 21 | mod git; 22 | mod process; 23 | mod resources; 24 | mod ui; 25 | 26 | fn main() { 27 | if let Err(err) = start() { 28 | eprintln!("{}", err.to_string().red()); 29 | std::process::exit(1); 30 | } 31 | } 32 | 33 | fn start() -> Result<(), HellNo> { 34 | let cli = Cli::parse(); 35 | 36 | if cli.verbose { 37 | simple_logger::init_with_level(log::Level::Debug)?; 38 | } 39 | 40 | let config = Config::load_default_user()?; 41 | log::debug!("ktsctl configuration:\n{config:#?}"); 42 | 43 | match cli.cmd { 44 | cli::Cmd::Fetch { all, lang } => { 45 | manage(config, true, false, false, false, all, lang.as_deref())? 46 | } 47 | 48 | cli::Cmd::Compile { all, lang } => { 49 | manage(config, false, true, false, false, all, lang.as_deref())? 50 | } 51 | 52 | cli::Cmd::Install { all, lang } => { 53 | manage(config, false, false, true, false, all, lang.as_deref())? 54 | } 55 | 56 | cli::Cmd::Sync { all, lang } => { 57 | manage(config, false, false, false, true, all, lang.as_deref())? 58 | } 59 | 60 | cli::Cmd::Query { lang, all } => { 61 | let query = Query::new(config)?; 62 | if let Some(lang) = lang { 63 | let sections = query.lang_info_sections(lang.as_str()); 64 | for sct in sections { 65 | println!("{sct}"); 66 | } 67 | } else if all { 68 | let all_tbl = query.all_lang_info_tbl(); 69 | println!("{all_tbl}"); 70 | } 71 | } 72 | 73 | cli::Cmd::Remove { 74 | mut grammar, 75 | mut queries, 76 | prune, 77 | lang, 78 | } => { 79 | let resources = Resources::new()?; 80 | if !grammar && !queries { 81 | grammar = true; 82 | queries = true; 83 | } 84 | 85 | remove::remove(&config, &resources, grammar, queries, prune, lang)?; 86 | } 87 | } 88 | 89 | Ok(()) 90 | } 91 | 92 | fn manage( 93 | config: Config, 94 | fetch: bool, 95 | compile: bool, 96 | install: bool, 97 | sync: bool, 98 | all: bool, 99 | lang: Option<&str>, 100 | ) -> Result<(), HellNo> { 101 | let manage_flags = ManageFlags::new(fetch, compile, install, sync); 102 | 103 | if let Some(lang) = lang { 104 | let manager = Manager::new(config, manage_flags)?; 105 | manager.manage(lang)?; 106 | } else if all { 107 | let all_langs: HashSet<_> = config.languages.language.keys().cloned().collect(); 108 | let manager = Manager::new(config, manage_flags)?; 109 | manager.manage_all(all_langs.iter().map(|s| s.as_str())); 110 | } 111 | 112 | Ok(()) 113 | } 114 | -------------------------------------------------------------------------------- /ktsctl/src/process.rs: -------------------------------------------------------------------------------- 1 | //! Process handling. 2 | 3 | use std::{ 4 | io::Read, 5 | path::Path, 6 | process::{Command, Stdio}, 7 | }; 8 | 9 | use crate::error::HellNo; 10 | 11 | /// Small wrapper above [`std::process::Command`]. 12 | /// 13 | /// A process is run by muting its stdout and piping its stderr. The exit code of the process is checked and if it’s 14 | /// not `0`, the content of its stderr file is returned in [`AppError`]. 15 | #[derive(Debug)] 16 | pub struct Process<'a> { 17 | name: &'a str, 18 | } 19 | 20 | impl<'a> Process<'a> { 21 | pub fn new(name: &'a str) -> Self { 22 | Self { name } 23 | } 24 | 25 | pub fn run<'b>(&self, cwd: impl Into>, args: &[&str]) -> Result<(), HellNo> { 26 | let process = format!("{} {}", self.name, args.join(" ")); 27 | let mut cmd = Command::new(self.name); 28 | cmd.args(args); 29 | 30 | if let Some(cwd) = cwd.into() { 31 | cmd.current_dir(cwd); 32 | } 33 | 34 | let mut child = cmd 35 | .stdout(Stdio::null()) 36 | .stderr(Stdio::piped()) 37 | .spawn() 38 | .map_err(|err| HellNo::ProcessRunError { 39 | process: process.clone(), 40 | err, 41 | })?; 42 | let stderr = child.stderr.take(); 43 | 44 | let exit_status = child.wait().map_err(|err| HellNo::ProcessRunError { 45 | process: process.clone(), 46 | err, 47 | })?; 48 | 49 | if !exit_status.success() { 50 | if let Some(mut stderr) = stderr { 51 | let mut err = String::new(); 52 | stderr 53 | .read_to_string(&mut err) 54 | .map_err(|err| HellNo::ProcessRunError { 55 | process: process.clone(), 56 | err, 57 | })?; 58 | 59 | return Err(HellNo::ProcessExitedWithError { 60 | process: process.clone(), 61 | err: err.to_string(), 62 | }); 63 | } 64 | } 65 | 66 | Ok(()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ktsctl/src/resources.rs: -------------------------------------------------------------------------------- 1 | //! Resources directories, files and various related functions. 2 | 3 | use std::path::{Path, PathBuf}; 4 | 5 | use kak_tree_sitter_config::{source::Source, LanguageConfig}; 6 | 7 | use crate::error::HellNo; 8 | 9 | /// Resources view (paths, dirs, etc.). 10 | #[derive(Debug)] 11 | pub struct Resources { 12 | runtime_dir: PathBuf, 13 | data_dir: PathBuf, 14 | } 15 | 16 | impl Resources { 17 | /// Ensure paths / directories exist and generate a [`Resources`] object. 18 | pub fn new() -> Result { 19 | let runtime_dir = dirs::runtime_dir() 20 | .or_else(|| std::env::var("TMPDIR").map(PathBuf::from).ok()) 21 | .ok_or_else(|| HellNo::NoRuntimeDir)? 22 | .join("ktsctl"); 23 | 24 | let data_dir = dirs::data_dir() 25 | .ok_or_else(|| HellNo::NoDataDir)? 26 | .join("kak-tree-sitter"); 27 | 28 | Ok(Self { 29 | runtime_dir, 30 | data_dir, 31 | }) 32 | } 33 | 34 | /// Data directory (a.k.a. install directory); where `ktsctl` moves resources. 35 | pub fn data_dir(&self) -> &Path { 36 | &self.data_dir 37 | } 38 | 39 | /// Source directory for a given URL (get a stable path for a given URL to work in). 40 | /// This function only supports http:// and https:// links. For instance, 41 | /// https://github.com/hadronized/kak-tree-sitter will get a directory created in the form of: 42 | /// 43 | /// /sources/github.com/hadronized/kak-tree-sitter 44 | /// 45 | /// Note: this function doesn’t perform any cleaning of the input URL, and it doesn’t perform any IO. 46 | pub fn sources_dir(&self, url: &str) -> PathBuf { 47 | let url_dir = PathBuf::from( 48 | url 49 | .trim_start_matches("http") 50 | .trim_start_matches('s') 51 | .trim_start_matches("://"), 52 | ); 53 | 54 | self.runtime_dir.join("sources").join(url_dir) 55 | } 56 | 57 | /// Build directory for building source code. 58 | pub fn lang_build_dir(&self, path: &Path, src: &Path) -> PathBuf { 59 | self.runtime_dir.join(format!( 60 | "{path}/{src}/build", 61 | path = path.display(), 62 | src = src.display() 63 | )) 64 | } 65 | 66 | /// Get a grammar path from config. 67 | pub fn grammar_path_from_config(&self, lang: &str, config: &LanguageConfig) -> PathBuf { 68 | match config.grammar.source { 69 | Source::Local { ref path } => path.clone(), 70 | Source::Git { ref pin, .. } => self.grammar_pin_path(lang, pin), 71 | } 72 | } 73 | 74 | /// Directory for language grammar and a pin. 75 | pub fn grammar_pin_path(&self, lang: &str, pin: &str) -> PathBuf { 76 | self.data_dir.join(format!("grammars/{lang}/{pin}.so")) 77 | } 78 | 79 | /// Get the queries directory from a language config. 80 | pub fn queries_dir_from_config(&self, lang: &str, config: &LanguageConfig) -> Option { 81 | let path = match config.queries.source.as_ref()? { 82 | Source::Local { ref path } => path.clone(), 83 | Source::Git { ref pin, .. } => self.queries_pin_dir(lang, pin), 84 | }; 85 | 86 | Some(path) 87 | } 88 | 89 | /// Directory for language queries. 90 | pub fn queries_pin_dir(&self, lang: &str, pin: &str) -> PathBuf { 91 | self.data_dir.join(format!("queries/{lang}/{pin}")) 92 | } 93 | 94 | /// Check if a grammar was compiled and installed for a given language and pin. 95 | pub fn grammar_exists(&self, lang: &str, pin: &str) -> bool { 96 | let path = self.grammar_pin_path(lang, pin); 97 | matches!(path.try_exists(), Ok(true)) 98 | } 99 | 100 | /// Check if queries exist for a given language and pin. 101 | /// 102 | /// Note: this function doesn’t check for the existence of specific queries; only that a directory exists for them. 103 | pub fn queries_exist(&self, lang: &str, pin: &str) -> bool { 104 | let path = self.queries_pin_dir(lang, pin); 105 | matches!(path.try_exists(), Ok(true)) 106 | } 107 | 108 | /// Grammar directory containing all grammars for a given language. 109 | pub fn grammars_dir(&self, lang: &str) -> PathBuf { 110 | self.data_dir.join(format!("grammars/{lang}")) 111 | } 112 | 113 | /// Queries directory containing all queries for a given language. 114 | pub fn queries_dir(&self, lang: &str) -> PathBuf { 115 | self.data_dir.join(format!("queries/{lang}")) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /ktsctl/src/ui.rs: -------------------------------------------------------------------------------- 1 | //! User Interface in the terminal. 2 | //! 3 | //! This module exports everything that is needed to provide feedback to the user. 4 | 5 | pub mod report; 6 | pub mod section; 7 | pub mod source; 8 | pub mod status_icon; 9 | pub mod table; 10 | -------------------------------------------------------------------------------- /ktsctl/src/ui/report.rs: -------------------------------------------------------------------------------- 1 | //! Report progress and status of commands. 2 | 3 | use std::io::{stdout, Write}; 4 | 5 | use super::status_icon::StatusIcon; 6 | 7 | /// A report that can updates itself on stdout. 8 | /// 9 | /// This type can be used whenever you want to output progress of a task on the same line. Every report will replace 10 | /// the current line, allowing an “in-place” report update, progress bar, etc. 11 | /// 12 | /// You use it with [`Report::new`] to create the initial line, and then use the [`Report::update()`] function to update 13 | /// the report, or one of the other methods to update the report. 14 | /// 15 | /// Once you are done and want to finish the report and go to the next line, simply drop the report. 16 | #[derive(Debug)] 17 | pub struct Report; 18 | 19 | impl Report { 20 | pub fn new(icon: StatusIcon, msg: impl AsRef) -> Self { 21 | print!("\x1b[?7l"); 22 | Self::to_stdout(icon, msg); 23 | Self 24 | } 25 | 26 | fn to_stdout(icon: StatusIcon, msg: impl AsRef) { 27 | print!("{} {msg}", icon, msg = msg.as_ref()); 28 | stdout().flush().unwrap(); 29 | } 30 | 31 | pub fn update(&self, icon: StatusIcon, msg: impl AsRef) { 32 | print!("\x1b[2K\r"); 33 | Self::to_stdout(icon, msg); 34 | } 35 | 36 | pub fn fetch(&self, msg: impl AsRef) { 37 | self.update(StatusIcon::Fetch, msg) 38 | } 39 | 40 | pub fn sync(&self, msg: impl AsRef) { 41 | self.update(StatusIcon::Sync, msg) 42 | } 43 | 44 | pub fn success(&self, msg: impl AsRef) { 45 | self.update(StatusIcon::Success, msg) 46 | } 47 | 48 | pub fn info(&self, msg: impl AsRef) { 49 | self.update(StatusIcon::Info, msg) 50 | } 51 | 52 | pub fn error(&self, msg: impl AsRef) { 53 | self.update(StatusIcon::Error, msg) 54 | } 55 | } 56 | 57 | impl Drop for Report { 58 | fn drop(&mut self) { 59 | println!("\x1b[?7h"); 60 | stdout().flush().unwrap(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ktsctl/src/ui/section.rs: -------------------------------------------------------------------------------- 1 | //! Typography based utilities. 2 | 3 | use std::fmt::Display; 4 | 5 | use colored::{ColoredString, Colorize}; 6 | 7 | use super::status_icon::StatusIcon; 8 | 9 | #[derive(Debug)] 10 | pub struct Section { 11 | name: String, 12 | fields: Vec, 13 | } 14 | 15 | impl Section { 16 | pub fn new(name: impl Into) -> Self { 17 | Self { 18 | name: name.into(), 19 | fields: Vec::default(), 20 | } 21 | } 22 | 23 | pub fn push(&mut self, field: Field) -> &mut Self { 24 | self.fields.push(field); 25 | self 26 | } 27 | } 28 | 29 | impl Display for Section { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | let name = format!("· {}", self.name).bold(); 32 | writeln!(f, "{name}")?; 33 | 34 | for fld in &self.fields { 35 | writeln!(f, " {fld}")?; 36 | } 37 | 38 | Ok(()) 39 | } 40 | } 41 | 42 | impl Extend for Section { 43 | fn extend>(&mut self, iter: T) { 44 | self.fields.extend(iter); 45 | } 46 | } 47 | 48 | #[derive(Debug)] 49 | pub struct SectionBuilder { 50 | section: Section, 51 | } 52 | 53 | impl SectionBuilder { 54 | pub fn new(name: impl Into) -> Self { 55 | Self { 56 | section: Section::new(name), 57 | } 58 | } 59 | 60 | pub fn push(mut self, field: Field) -> Self { 61 | self.section.push(field); 62 | self 63 | } 64 | 65 | pub fn build(self) -> Section { 66 | self.section 67 | } 68 | } 69 | 70 | /// Section field. 71 | #[derive(Debug)] 72 | pub enum Field { 73 | KeyValue { 74 | key: ColoredString, 75 | value: FieldValue, 76 | indent: usize, 77 | }, 78 | 79 | StatusLine { 80 | status: StatusIcon, 81 | value: FieldValue, 82 | indent: usize, 83 | }, 84 | } 85 | 86 | impl Field { 87 | pub fn kv(key: ColoredString, value: impl Into) -> Self { 88 | Self::KeyValue { 89 | key, 90 | value: value.into(), 91 | indent: 0, 92 | } 93 | } 94 | 95 | pub fn status_line(status: StatusIcon, value: impl Into) -> Self { 96 | Self::StatusLine { 97 | status, 98 | value: value.into(), 99 | indent: 0, 100 | } 101 | } 102 | 103 | pub fn indent(&mut self) -> &mut Self { 104 | match self { 105 | Self::KeyValue { indent, .. } => *indent += 1, 106 | Self::StatusLine { indent, .. } => *indent += 1, 107 | } 108 | 109 | self 110 | } 111 | } 112 | 113 | impl Display for Field { 114 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 115 | match self { 116 | Field::KeyValue { key, value, indent } => write!( 117 | f, 118 | "{indent}{key}{delim} {value}", 119 | delim = ":".black(), 120 | indent = " ".repeat(*indent * 2) 121 | ), 122 | Field::StatusLine { 123 | status, 124 | value, 125 | indent, 126 | } => write!( 127 | f, 128 | "{indent}{status} {value}", 129 | indent = " ".repeat(*indent * 2) 130 | ), 131 | } 132 | } 133 | } 134 | 135 | #[derive(Debug)] 136 | pub enum FieldValue { 137 | String(ColoredString), 138 | List(Vec), 139 | } 140 | 141 | impl FieldValue { 142 | pub fn list(v: impl Into>) -> Self { 143 | Self::List(v.into()) 144 | } 145 | } 146 | 147 | impl Display for FieldValue { 148 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 149 | match self { 150 | FieldValue::String(s) => s.fmt(f), 151 | FieldValue::List(ss) => { 152 | write!(f, "{} ", "[".black())?; 153 | 154 | if !ss.is_empty() { 155 | write!(f, "{}", ss[0])?; 156 | 157 | for s in &ss[1..] { 158 | write!(f, "{} {}", ",".black(), s)?; 159 | } 160 | } 161 | 162 | write!(f, " {}", "]".black()) 163 | } 164 | } 165 | } 166 | } 167 | 168 | impl From for FieldValue 169 | where 170 | T: Into, 171 | { 172 | fn from(value: T) -> Self { 173 | FieldValue::String(value.into()) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /ktsctl/src/ui/source.rs: -------------------------------------------------------------------------------- 1 | //! Source UI. 2 | 3 | use colored::Colorize; 4 | use kak_tree_sitter_config::source::Source; 5 | 6 | use super::section::Field; 7 | 8 | pub fn source_field(source: &Source) -> Field { 9 | match source { 10 | Source::Local { path } => Field::kv("Source (path)".blue(), path.display().to_string().green()), 11 | Source::Git { url, pin } => Field::kv( 12 | "Source (git)".blue(), 13 | format!( 14 | "{} {}{}{}", 15 | url.green(), 16 | "(".black(), 17 | pin.cyan(), 18 | ")".black() 19 | ), 20 | ), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ktsctl/src/ui/status_icon.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use colored::{ColoredString, Colorize}; 4 | 5 | #[derive(Debug)] 6 | pub enum StatusIcon { 7 | Fetch, 8 | Sync, 9 | Compile, 10 | Link, 11 | Install, 12 | Success, 13 | Error, 14 | Warn, 15 | Info, 16 | } 17 | 18 | impl From for ColoredString { 19 | fn from(value: StatusIcon) -> Self { 20 | match value { 21 | StatusIcon::Fetch => "".magenta(), 22 | StatusIcon::Sync => "".magenta(), 23 | StatusIcon::Compile => "".cyan(), 24 | StatusIcon::Link => "".cyan(), 25 | StatusIcon::Install => "".cyan(), 26 | StatusIcon::Success => "".green(), 27 | StatusIcon::Error => "".red(), 28 | StatusIcon::Warn => "".yellow(), 29 | StatusIcon::Info => "󰈅".blue(), 30 | } 31 | } 32 | } 33 | 34 | impl Display for StatusIcon { 35 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 36 | match self { 37 | StatusIcon::Fetch => write!(f, "{}", "".magenta()), 38 | StatusIcon::Sync => write!(f, "{}", "".magenta()), 39 | StatusIcon::Compile => write!(f, "{}", "".cyan()), 40 | StatusIcon::Link => write!(f, "{}", "".cyan()), 41 | StatusIcon::Install => write!(f, "{}", "".cyan()), 42 | StatusIcon::Success => write!(f, "{}", "".green()), 43 | StatusIcon::Error => write!(f, "{}", "".red()), 44 | StatusIcon::Warn => write!(f, "{}", "".yellow()), 45 | StatusIcon::Info => write!(f, "{}", "󰈅".blue()), 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ktsctl/src/ui/table.rs: -------------------------------------------------------------------------------- 1 | //! Output table with header and rows. 2 | 3 | use std::fmt::Display; 4 | 5 | use colored::ColoredString; 6 | use unicode_segmentation::UnicodeSegmentation; 7 | 8 | /// A table containing a header and rows. 9 | /// 10 | /// Use the [`Display`] implementor to write it to a string. 11 | #[derive(Debug, Default)] 12 | pub struct Table { 13 | header: Row, 14 | rows: Vec, 15 | } 16 | 17 | impl Table { 18 | /// Compute the maximum length for each column. 19 | fn max_len_by_col(&self) -> impl '_ + Iterator { 20 | (0..self.header.cells.len()).map(|i| { 21 | let max_len = self.header.cells[i].content.graphemes(true).count(); 22 | self.rows.iter().fold(max_len, |max_len, row| { 23 | row.cells[i].content.graphemes(true).count().max(max_len) 24 | }) 25 | }) 26 | } 27 | 28 | /// Set the header. 29 | pub fn header(&mut self, header: Row) -> &mut Self { 30 | self.header = header; 31 | self 32 | } 33 | 34 | /// Add a row to the table. 35 | pub fn push(&mut self, row: Row) -> &mut Self { 36 | self.rows.push(row); 37 | self 38 | } 39 | } 40 | 41 | impl Display for Table { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | let lengths: Vec<_> = self.max_len_by_col().collect(); 44 | 45 | // header first 46 | self.header.display_with_len(&lengths, f)?; 47 | 48 | // delimiter 49 | let delim = vec![Cell::new("").fill('-'); self.header.cells.len()]; 50 | Row { cells: delim }.display_with_len(&lengths, f)?; 51 | 52 | // then rows 53 | for row in &self.rows { 54 | row.display_with_len(&lengths, f)?; 55 | } 56 | 57 | Ok(()) 58 | } 59 | } 60 | 61 | /// A cell inside a table. 62 | /// 63 | /// It can be used as header or row values. 64 | #[derive(Clone, Debug)] 65 | pub struct Cell { 66 | content: ColoredString, 67 | filling: char, 68 | } 69 | 70 | impl Cell { 71 | pub fn new(content: impl Into) -> Self { 72 | Self { 73 | content: content.into(), 74 | filling: ' ', 75 | } 76 | } 77 | 78 | pub fn fill(mut self, filling: char) -> Self { 79 | self.filling = filling; 80 | self 81 | } 82 | 83 | /// Display the cell with specific length padding. 84 | fn display_with_len(&self, len: usize, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 85 | let fill = self.filling; 86 | // BUG: here, the problem is that we are displaying something that has a style applied to it; I don’t get why it’s 87 | // fucked up like that, maybe we should be using something more “display” like? 88 | let padding_len = len - self.content.graphemes(true).count(); 89 | let content = &self.content; 90 | 91 | write!(f, "{content}",)?; 92 | 93 | for _ in 0..padding_len { 94 | write!(f, "{fill}")?; 95 | } 96 | 97 | Ok(()) 98 | } 99 | } 100 | 101 | impl<'a> From<&'a str> for Cell { 102 | fn from(s: &'a str) -> Self { 103 | Self::new(s) 104 | } 105 | } 106 | 107 | /// A row of cells. 108 | #[derive(Debug, Default)] 109 | pub struct Row { 110 | cells: Vec, 111 | } 112 | 113 | impl Row { 114 | pub fn push(&mut self, cell: impl Into) -> &mut Self { 115 | self.cells.push(cell.into()); 116 | self 117 | } 118 | 119 | fn display_with_len(&self, lens: &[usize], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 120 | if self.cells.is_empty() { 121 | return Ok(()); 122 | } 123 | 124 | // first cell first 125 | self.cells[0].display_with_len(lens[0], f)?; 126 | 127 | // then the rest 128 | for (cell, &len) in self.cells[1..].iter().zip(&lens[1..]) { 129 | f.write_str(" | ")?; 130 | cell.display_with_len(len, f)?; 131 | } 132 | 133 | f.write_str("\n") 134 | } 135 | } 136 | 137 | #[derive(Debug, Default)] 138 | pub struct RowBuilder { 139 | row: Row, 140 | } 141 | 142 | impl RowBuilder { 143 | pub fn push(mut self, cell: impl Into) -> Self { 144 | self.row.push(cell); 145 | self 146 | } 147 | 148 | pub fn build(self) -> Row { 149 | self.row 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /runtime/README.md: -------------------------------------------------------------------------------- 1 | # Runtime tree-sitter queries 2 | 3 | The queries are not shipped with `kak-tree-sitter`. Instead, you have to manually install them. You have two solutions: 4 | 5 | 1. Deal with installing queries by yourself, or even hand-craft them. **This is not recommended.** 6 | 2. Use `ktsctl` and the `config.toml` file to install the queries by fetching them (`git clone`). 7 | 8 | For the second option, the default [config.toml](/config.toml) is already configured to point to well-working grammars 9 | and queries. You should copy it as `$XDG_CONFIG_HOME/kak-tree-sitter/config.toml`. 10 | 11 | ## Special case for locally modified queries 12 | 13 | Some queries required to be checked-in in this repository, and manually modified. Most of the queries are taken 14 | from [Helix](https://github.com/helix-editor/helix/tree/master/runtime/queries), and sometimes we have to adapt them 15 | a little bit to make them work with `kak-tree-sitter`. 16 | 17 | Those queries are available in [the runtime/queries](./queries) directory, along with a `README.md` explaining their 18 | sources, the license used and a `LICENSE` file applying to all of the `.scm` files in the associated language directory. 19 | -------------------------------------------------------------------------------- /runtime/queries/astro/README.md: -------------------------------------------------------------------------------- 1 | All the files in this directory were taken and adapted from 2 | [Helix Astro queries](https://github.com/helix-editor/helix/tree/master/runtime/queries/astro) and [Helix HTML queries](https://github.com/helix-editor/helix/tree/master/runtime/queries/html). 3 | They are thus licensed under the [Mozilla Public License 2.0](https://github.com/helix-editor/helix/blob/master/LICENSE). 4 | -------------------------------------------------------------------------------- /runtime/queries/astro/highlights.scm: -------------------------------------------------------------------------------- 1 | (tag_name) @tag 2 | (erroneous_end_tag_name) @tag.error 3 | (doctype) @constant 4 | (attribute_name) @attribute 5 | (comment) @comment 6 | 7 | [ 8 | "\"" 9 | (attribute_value) 10 | ] @string 11 | 12 | [ 13 | "<" 14 | ">" 15 | "" 17 | ""] @punctuation.bracket)) 57 | (template_parameter_list (["<" ">"] @punctuation.bracket)) 58 | (default_method_clause "default" @keyword) 59 | 60 | "static_assert" @function.special 61 | 62 | [ 63 | "<=>" 64 | "[]" 65 | "()" 66 | ] @operator 67 | 68 | [ 69 | "co_await" 70 | "co_return" 71 | "co_yield" 72 | "concept" 73 | "delete" 74 | "new" 75 | "operator" 76 | "requires" 77 | "using" 78 | ] @keyword 79 | 80 | [ 81 | "catch" 82 | "noexcept" 83 | "throw" 84 | "try" 85 | ] @keyword.control.exception 86 | 87 | 88 | [ 89 | "and" 90 | "and_eq" 91 | "bitor" 92 | "bitand" 93 | "not" 94 | "not_eq" 95 | "or" 96 | "or_eq" 97 | "xor" 98 | "xor_eq" 99 | ] @keyword.operator 100 | 101 | [ 102 | "class" 103 | "namespace" 104 | "typename" 105 | "template" 106 | ] @keyword.storage.type 107 | 108 | [ 109 | "constexpr" 110 | "constinit" 111 | "consteval" 112 | "mutable" 113 | ] @keyword.storage.modifier 114 | 115 | ; Modifiers that aren't plausibly type/storage related. 116 | [ 117 | "explicit" 118 | "friend" 119 | "virtual" 120 | (virtual_specifier) ; override/final 121 | "private" 122 | "protected" 123 | "public" 124 | "inline" ; C++ meaning differs from C! 125 | ] @keyword 126 | 127 | ; Strings 128 | 129 | (raw_string_literal) @string 130 | 131 | "sizeof" @keyword 132 | 133 | [ 134 | "enum" 135 | "struct" 136 | "typedef" 137 | "union" 138 | ] @keyword.storage.type 139 | 140 | [ 141 | "extern" 142 | "register" 143 | (type_qualifier) 144 | (storage_class_specifier) 145 | ] @keyword.storage.modifier 146 | 147 | [ 148 | "goto" 149 | "break" 150 | "continue" 151 | ] @keyword.control 152 | 153 | [ 154 | "do" 155 | "for" 156 | "while" 157 | ] @keyword.control.repeat 158 | 159 | [ 160 | "if" 161 | "else" 162 | "switch" 163 | "case" 164 | "default" 165 | ] @keyword.control.conditional 166 | 167 | "return" @keyword.control.return 168 | 169 | [ 170 | "defined" 171 | "#define" 172 | "#elif" 173 | "#else" 174 | "#endif" 175 | "#if" 176 | "#ifdef" 177 | "#ifndef" 178 | "#include" 179 | (preproc_directive) 180 | ] @keyword.directive 181 | 182 | (pointer_declarator "*" @type.builtin) 183 | (abstract_pointer_declarator "*" @type.builtin) 184 | 185 | [ 186 | "+" 187 | "-" 188 | "*" 189 | "/" 190 | "++" 191 | "--" 192 | "%" 193 | "==" 194 | "!=" 195 | ">" 196 | "<" 197 | ">=" 198 | "<=" 199 | "&&" 200 | "||" 201 | "!" 202 | "&" 203 | "|" 204 | "^" 205 | "~" 206 | "<<" 207 | ">>" 208 | "=" 209 | "+=" 210 | "-=" 211 | "*=" 212 | "/=" 213 | "%=" 214 | "<<=" 215 | ">>=" 216 | "&=" 217 | "^=" 218 | "|=" 219 | "?" 220 | ] @operator 221 | 222 | (conditional_expression ":" @operator) 223 | 224 | "..." @punctuation 225 | 226 | ["," "." ":" ";" "->" "::"] @punctuation.delimiter 227 | 228 | ["(" ")" "[" "]" "{" "}"] @punctuation.bracket 229 | 230 | [(true) (false)] @constant.builtin.boolean 231 | 232 | (enumerator name: (identifier) @type.enum.variant) 233 | 234 | (string_literal) @string 235 | (system_lib_string) @string 236 | 237 | (null) @constant 238 | (number_literal) @constant.numeric 239 | (char_literal) @constant.character 240 | (escape_sequence) @constant.character.escape 241 | 242 | (call_expression 243 | function: (identifier) @function) 244 | (call_expression 245 | function: (field_expression 246 | field: (field_identifier) @function)) 247 | (call_expression (argument_list (identifier) @variable)) 248 | (function_declarator 249 | declarator: [(identifier) (field_identifier)] @function) 250 | (parameter_declaration 251 | declarator: (identifier) @variable.parameter) 252 | (parameter_declaration 253 | (pointer_declarator 254 | declarator: (identifier) @variable.parameter)) 255 | (preproc_function_def 256 | name: (identifier) @function.special) 257 | 258 | (attribute 259 | name: (identifier) @attribute) 260 | 261 | (field_identifier) @variable.other.member 262 | (statement_identifier) @label 263 | (type_identifier) @type 264 | (primitive_type) @type.builtin 265 | (sized_type_specifier) @type.builtin 266 | 267 | ((identifier) @constant 268 | (#match? @constant "^[A-Z][A-Z\\d_]*$")) 269 | 270 | (identifier) @variable 271 | 272 | (comment) @comment 273 | -------------------------------------------------------------------------------- /runtime/queries/cpp/indents.scm: -------------------------------------------------------------------------------- 1 | ; kak-tree-sitter notes: taken from helix/helix-editor 2 | 3 | [ 4 | (compound_statement) 5 | (declaration_list) 6 | (field_declaration_list) 7 | (enumerator_list) 8 | (parameter_list) 9 | (init_declarator) 10 | (case_statement) 11 | (expression_statement) 12 | ] @indent 13 | 14 | [ 15 | "case" 16 | "}" 17 | "]" 18 | ] @outdent 19 | 20 | (if_statement 21 | consequence: (_) @indent 22 | (#not-kind-eq? @indent "compound_statement") 23 | (#set! "scope" "all")) 24 | (while_statement 25 | body: (_) @indent 26 | (#not-kind-eq? @indent "compound_statement") 27 | (#set! "scope" "all")) 28 | (do_statement 29 | body: (_) @indent 30 | (#not-kind-eq? @indent "compound_statement") 31 | (#set! "scope" "all")) 32 | (for_statement 33 | ")" 34 | (_) @indent 35 | (#not-kind-eq? @indent "compound_statement") 36 | (#set! "scope" "all")) 37 | 38 | (access_specifier) @outdent 39 | -------------------------------------------------------------------------------- /runtime/queries/cpp/injections.scm: -------------------------------------------------------------------------------- 1 | ; kak-tree-sitter notes: taken from helix/helix-editor 2 | 3 | ((comment) @injection.content 4 | (#set! injection.language "comment")) 5 | 6 | (raw_string_literal 7 | delimiter: (raw_string_delimiter) @injection.language 8 | (raw_string_content) @injection.content) 9 | -------------------------------------------------------------------------------- /runtime/queries/cpp/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; kak-tree-sitter notes: taken from helix/helix-editor 2 | 3 | (function_definition 4 | body: (_) @function.inside) @function.around 5 | 6 | (struct_specifier 7 | body: (_) @class.inside) @class.around 8 | 9 | (enum_specifier 10 | body: (_) @class.inside) @class.around 11 | 12 | (union_specifier 13 | body: (_) @class.inside) @class.around 14 | 15 | (parameter_list 16 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 17 | 18 | (argument_list 19 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 20 | 21 | (comment) @comment.inside 22 | 23 | (comment)+ @comment.around 24 | 25 | (lambda_expression 26 | body: (_) @function.inside) @function.around 27 | 28 | (class_specifier 29 | body: (_) @class.inside) @class.around 30 | -------------------------------------------------------------------------------- /runtime/queries/javascript/README.md: -------------------------------------------------------------------------------- 1 | All the files in this directory were taken and adapted from 2 | [Helix Javascript queries](https://github.com/helix-editor/helix/tree/master/runtime/queries/javascript) and are then 3 | licensed under the [Mozilla Public License 2.0](https://github.com/helix-editor/helix/blob/master/LICENSE). 4 | -------------------------------------------------------------------------------- /runtime/queries/javascript/highlights.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Special identifiers 3 | ;-------------------- 4 | 5 | ([ 6 | (identifier) 7 | (shorthand_property_identifier) 8 | (shorthand_property_identifier_pattern) 9 | ] @constant 10 | (#match? @constant "^[A-Z_][A-Z\\d_]+$")) 11 | 12 | 13 | ((identifier) @constructor 14 | (#match? @constructor "^[A-Z]")) 15 | 16 | ((identifier) @variable.builtin 17 | (#match? @variable.builtin "^(arguments|module|console|window|document)$") 18 | (#is-not? local)) 19 | 20 | ((identifier) @function.builtin 21 | (#eq? @function.builtin "require") 22 | (#is-not? local)) 23 | 24 | ; Function and method definitions 25 | ;-------------------------------- 26 | 27 | (function 28 | name: (identifier) @function) 29 | (function_declaration 30 | name: (identifier) @function) 31 | (method_definition 32 | name: (property_identifier) @function.method) 33 | 34 | (pair 35 | key: (property_identifier) @function.method 36 | value: [(function) (arrow_function)]) 37 | 38 | (assignment_expression 39 | left: (member_expression 40 | property: (property_identifier) @function.method) 41 | right: [(function) (arrow_function)]) 42 | 43 | (variable_declarator 44 | name: (identifier) @function 45 | value: [(function) (arrow_function)]) 46 | 47 | (assignment_expression 48 | left: (identifier) @function 49 | right: [(function) (arrow_function)]) 50 | 51 | ; Function and method parameters 52 | ;------------------------------- 53 | 54 | ; Arrow function parameters in the form `p => ...` are supported by both 55 | ; javascript and typescript grammars without conflicts. 56 | (arrow_function 57 | parameter: (identifier) @variable.parameter) 58 | 59 | ; Function and method calls 60 | ;-------------------------- 61 | 62 | (call_expression 63 | function: (identifier) @function) 64 | 65 | (call_expression 66 | function: (member_expression 67 | property: (property_identifier) @function.method)) 68 | 69 | ; Variables 70 | ;---------- 71 | 72 | (identifier) @variable 73 | 74 | ; Properties 75 | ;----------- 76 | 77 | (property_identifier) @variable.other.member 78 | (shorthand_property_identifier) @variable.other.member 79 | (shorthand_property_identifier_pattern) @variable.other.member 80 | 81 | ; Literals 82 | ;--------- 83 | 84 | (this) @variable.builtin 85 | (super) @variable.builtin 86 | 87 | [ 88 | (true) 89 | (false) 90 | (null) 91 | (undefined) 92 | ] @constant.builtin 93 | 94 | (comment) @comment 95 | 96 | [ 97 | (string) 98 | (template_string) 99 | ] @string 100 | 101 | (regex) @string.regexp 102 | (number) @constant.numeric.integer 103 | 104 | ; Tokens 105 | ;------- 106 | 107 | (template_substitution 108 | "${" @punctuation.special 109 | "}" @punctuation.special) @embedded 110 | 111 | [ 112 | ";" 113 | (optional_chain) ; ?. 114 | "." 115 | "," 116 | ] @punctuation.delimiter 117 | 118 | [ 119 | "-" 120 | "--" 121 | "-=" 122 | "+" 123 | "++" 124 | "+=" 125 | "*" 126 | "*=" 127 | "**" 128 | "**=" 129 | "/" 130 | "/=" 131 | "%" 132 | "%=" 133 | "<" 134 | "<=" 135 | "<<" 136 | "<<=" 137 | "=" 138 | "==" 139 | "===" 140 | "!" 141 | "!=" 142 | "!==" 143 | "=>" 144 | ">" 145 | ">=" 146 | ">>" 147 | ">>=" 148 | ">>>" 149 | ">>>=" 150 | "~" 151 | "^" 152 | "&" 153 | "|" 154 | "^=" 155 | "&=" 156 | "|=" 157 | "&&" 158 | "||" 159 | "??" 160 | "&&=" 161 | "||=" 162 | "??=" 163 | "..." 164 | ] @operator 165 | 166 | (ternary_expression ["?" ":"] @operator) 167 | 168 | [ 169 | "(" 170 | ")" 171 | "[" 172 | "]" 173 | "{" 174 | "}" 175 | ] @punctuation.bracket 176 | 177 | [ 178 | "async" 179 | "debugger" 180 | "delete" 181 | "extends" 182 | "from" 183 | "get" 184 | "new" 185 | "set" 186 | "target" 187 | "typeof" 188 | "instanceof" 189 | "void" 190 | "with" 191 | ] @keyword 192 | 193 | [ 194 | "of" 195 | "as" 196 | "in" 197 | ] @keyword.operator 198 | 199 | [ 200 | "function" 201 | ] @keyword.function 202 | 203 | [ 204 | "class" 205 | "let" 206 | "var" 207 | ] @keyword.storage.type 208 | 209 | [ 210 | "const" 211 | "static" 212 | ] @keyword.storage.modifier 213 | 214 | [ 215 | "default" 216 | "yield" 217 | "finally" 218 | "do" 219 | "await" 220 | ] @keyword.control 221 | 222 | [ 223 | "if" 224 | "else" 225 | "switch" 226 | "case" 227 | "while" 228 | ] @keyword.control.conditional 229 | 230 | [ 231 | "for" 232 | ] @keyword.control.repeat 233 | 234 | [ 235 | "import" 236 | "export" 237 | ] @keyword.control.import 238 | 239 | [ 240 | "return" 241 | "break" 242 | "continue" 243 | ] @keyword.control.return 244 | 245 | [ 246 | "throw" 247 | "try" 248 | "catch" 249 | ] @keyword.control.exception 250 | 251 | ; javascript 252 | ; Function and method parameters 253 | ;------------------------------- 254 | 255 | ; Javascript and Typescript Treesitter grammars deviate when defining the 256 | ; tree structure for parameters, so we need to address them in each specific 257 | ; language instead of ecma. 258 | 259 | ; (p) 260 | (formal_parameters 261 | (identifier) @variable.parameter) 262 | 263 | ; (...p) 264 | (formal_parameters 265 | (rest_pattern 266 | (identifier) @variable.parameter)) 267 | 268 | ; ({ p }) 269 | (formal_parameters 270 | (object_pattern 271 | (shorthand_property_identifier_pattern) @variable.parameter)) 272 | 273 | ; ({ a: p }) 274 | (formal_parameters 275 | (object_pattern 276 | (pair_pattern 277 | value: (identifier) @variable.parameter))) 278 | 279 | ; ([ p ]) 280 | (formal_parameters 281 | (array_pattern 282 | (identifier) @variable.parameter)) 283 | 284 | ; (p = 1) 285 | (formal_parameters 286 | (assignment_pattern 287 | left: (identifier) @variable.parameter)) 288 | -------------------------------------------------------------------------------- /runtime/queries/javascript/indents.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | [ 3 | (array) 4 | (object) 5 | (arguments) 6 | (formal_parameters) 7 | 8 | (statement_block) 9 | (switch_statement) 10 | (object_pattern) 11 | (class_body) 12 | (named_imports) 13 | 14 | (binary_expression) 15 | (return_statement) 16 | (template_substitution) 17 | (export_clause) 18 | ] @indent 19 | 20 | [ 21 | (switch_case) 22 | (switch_default) 23 | ] @indent @extend 24 | 25 | [ 26 | "}" 27 | "]" 28 | ")" 29 | ] @outdent 30 | 31 | ; javascript -------------------------------------------------------------------------------- /runtime/queries/javascript/injections.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Parse the contents of tagged template literals using 3 | ; a language inferred from the tag. 4 | 5 | (call_expression 6 | function: [ 7 | (identifier) @injection.language 8 | (member_expression 9 | property: (property_identifier) @injection.language) 10 | ] 11 | arguments: (template_string) @injection.content) 12 | 13 | ; Parse the contents of gql template literals 14 | 15 | ((call_expression 16 | function: (identifier) @_template_function_name 17 | arguments: (template_string) @injection.content) 18 | (#eq? @_template_function_name "gql") 19 | (#set! injection.language "graphql")) 20 | 21 | ; Parse regex syntax within regex literals 22 | 23 | ((regex_pattern) @injection.content 24 | (#set! injection.language "regex")) 25 | 26 | ; Parse JSDoc annotations in multiline comments 27 | 28 | ((comment) @injection.content 29 | (#set! injection.language "jsdoc") 30 | (#match? @injection.content "^/\\*+")) 31 | 32 | ; Parse general tags in single line comments 33 | 34 | ((comment) @injection.content 35 | (#set! injection.language "comment") 36 | (#match? @injection.content "^//")) 37 | 38 | ; javascript -------------------------------------------------------------------------------- /runtime/queries/javascript/locals.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Scopes 3 | ;------- 4 | 5 | [ 6 | (statement_block) 7 | (function) 8 | (arrow_function) 9 | (function_declaration) 10 | (method_definition) 11 | ] @local.scope 12 | 13 | ; Definitions 14 | ;------------ 15 | 16 | ; ...i 17 | (rest_pattern 18 | (identifier) @local.definition) 19 | 20 | ; { i } 21 | (object_pattern 22 | (shorthand_property_identifier_pattern) @local.definition) 23 | 24 | ; { a: i } 25 | (object_pattern 26 | (pair_pattern 27 | value: (identifier) @local.definition)) 28 | 29 | ; [ i ] 30 | (array_pattern 31 | (identifier) @local.definition) 32 | 33 | ; i => ... 34 | (arrow_function 35 | parameter: (identifier) @local.definition) 36 | 37 | ; const/let/var i = ... 38 | (variable_declarator 39 | name: (identifier) @local.definition) 40 | 41 | ; References 42 | ;------------ 43 | 44 | (identifier) @local.reference 45 | 46 | ; javascript 47 | ; Definitions 48 | ;------------ 49 | ; Javascript and Typescript Treesitter grammars deviate when defining the 50 | ; tree structure for parameters, so we need to address them in each specific 51 | ; language instead of ecma. 52 | 53 | ; (i) 54 | (formal_parameters 55 | (identifier) @local.definition) 56 | 57 | ; (i = 1) 58 | (formal_parameters 59 | (assignment_pattern 60 | left: (identifier) @local.definition)) 61 | -------------------------------------------------------------------------------- /runtime/queries/javascript/tags.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | 3 | ; javascript 4 | ( 5 | (comment)* @doc 6 | . 7 | (method_definition 8 | name: (property_identifier) @name) @definition.method 9 | (#not-eq? @name "constructor") 10 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 11 | (#select-adjacent! @doc @definition.method) 12 | ) 13 | 14 | ( 15 | (comment)* @doc 16 | . 17 | [ 18 | (class 19 | name: (_) @name) 20 | (class_declaration 21 | name: (_) @name) 22 | ] @definition.class 23 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 24 | (#select-adjacent! @doc @definition.class) 25 | ) 26 | 27 | ( 28 | (comment)* @doc 29 | . 30 | [ 31 | (function 32 | name: (identifier) @name) 33 | (function_declaration 34 | name: (identifier) @name) 35 | (generator_function 36 | name: (identifier) @name) 37 | (generator_function_declaration 38 | name: (identifier) @name) 39 | ] @definition.function 40 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 41 | (#select-adjacent! @doc @definition.function) 42 | ) 43 | 44 | ( 45 | (comment)* @doc 46 | . 47 | (lexical_declaration 48 | (variable_declarator 49 | name: (identifier) @name 50 | value: [(arrow_function) (function)]) @definition.function) 51 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 52 | (#select-adjacent! @doc @definition.function) 53 | ) 54 | 55 | ( 56 | (comment)* @doc 57 | . 58 | (variable_declaration 59 | (variable_declarator 60 | name: (identifier) @name 61 | value: [(arrow_function) (function)]) @definition.function) 62 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 63 | (#select-adjacent! @doc @definition.function) 64 | ) 65 | 66 | (assignment_expression 67 | left: [ 68 | (identifier) @name 69 | (member_expression 70 | property: (property_identifier) @name) 71 | ] 72 | right: [(arrow_function) (function)] 73 | ) @definition.function 74 | 75 | (pair 76 | key: (property_identifier) @name 77 | value: [(arrow_function) (function)]) @definition.function 78 | 79 | ( 80 | (call_expression 81 | function: (identifier) @name) @reference.call 82 | (#not-match? @name "^(require)$") 83 | ) 84 | 85 | (call_expression 86 | function: (member_expression 87 | property: (property_identifier) @name) 88 | arguments: (_) @reference.call) 89 | 90 | (new_expression 91 | constructor: (_) @name) @reference.class -------------------------------------------------------------------------------- /runtime/queries/javascript/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | (function_declaration 3 | body: (_) @function.inside) @function.around 4 | 5 | (function 6 | body: (_) @function.inside) @function.around 7 | 8 | (arrow_function 9 | body: (_) @function.inside) @function.around 10 | 11 | (method_definition 12 | body: (_) @function.inside) @function.around 13 | 14 | (generator_function_declaration 15 | body: (_) @function.inside) @function.around 16 | 17 | (class_declaration 18 | body: (class_body) @class.inside) @class.around 19 | 20 | (class 21 | (class_body) @class.inside) @class.around 22 | 23 | (export_statement 24 | declaration: [ 25 | (function_declaration) @function.around 26 | (class_declaration) @class.around 27 | ]) 28 | 29 | (formal_parameters 30 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 31 | 32 | (arguments 33 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 34 | 35 | (comment) @comment.inside 36 | 37 | (comment)+ @comment.around 38 | 39 | ; javascript 40 | -------------------------------------------------------------------------------- /runtime/queries/jsx/README.md: -------------------------------------------------------------------------------- 1 | All the files in this directory were taken and adapted from 2 | [Helix JSX queries](https://github.com/helix-editor/helix/tree/master/runtime/queries/jsx) and are then 3 | licensed under the [Mozilla Public License 2.0](https://github.com/helix-editor/helix/blob/master/LICENSE). 4 | -------------------------------------------------------------------------------- /runtime/queries/jsx/highlights.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Special identifiers 3 | ;-------------------- 4 | 5 | ([ 6 | (identifier) 7 | (shorthand_property_identifier) 8 | (shorthand_property_identifier_pattern) 9 | ] @constant 10 | (#match? @constant "^[A-Z_][A-Z\\d_]+$")) 11 | 12 | 13 | ((identifier) @constructor 14 | (#match? @constructor "^[A-Z]")) 15 | 16 | ((identifier) @variable.builtin 17 | (#match? @variable.builtin "^(arguments|module|console|window|document)$") 18 | (#is-not? local)) 19 | 20 | ((identifier) @function.builtin 21 | (#eq? @function.builtin "require") 22 | (#is-not? local)) 23 | 24 | ; Function and method definitions 25 | ;-------------------------------- 26 | 27 | (function 28 | name: (identifier) @function) 29 | (function_declaration 30 | name: (identifier) @function) 31 | (method_definition 32 | name: (property_identifier) @function.method) 33 | 34 | (pair 35 | key: (property_identifier) @function.method 36 | value: [(function) (arrow_function)]) 37 | 38 | (assignment_expression 39 | left: (member_expression 40 | property: (property_identifier) @function.method) 41 | right: [(function) (arrow_function)]) 42 | 43 | (variable_declarator 44 | name: (identifier) @function 45 | value: [(function) (arrow_function)]) 46 | 47 | (assignment_expression 48 | left: (identifier) @function 49 | right: [(function) (arrow_function)]) 50 | 51 | ; Function and method parameters 52 | ;------------------------------- 53 | 54 | ; Arrow function parameters in the form `p => ...` are supported by both 55 | ; javascript and typescript grammars without conflicts. 56 | (arrow_function 57 | parameter: (identifier) @variable.parameter) 58 | 59 | ; Function and method calls 60 | ;-------------------------- 61 | 62 | (call_expression 63 | function: (identifier) @function) 64 | 65 | (call_expression 66 | function: (member_expression 67 | property: (property_identifier) @function.method)) 68 | 69 | ; Variables 70 | ;---------- 71 | 72 | (identifier) @variable 73 | 74 | ; Properties 75 | ;----------- 76 | 77 | (property_identifier) @variable.other.member 78 | (shorthand_property_identifier) @variable.other.member 79 | (shorthand_property_identifier_pattern) @variable.other.member 80 | 81 | ; Literals 82 | ;--------- 83 | 84 | (this) @variable.builtin 85 | (super) @variable.builtin 86 | 87 | [ 88 | (true) 89 | (false) 90 | (null) 91 | (undefined) 92 | ] @constant.builtin 93 | 94 | (comment) @comment 95 | 96 | [ 97 | (string) 98 | (template_string) 99 | ] @string 100 | 101 | (regex) @string.regexp 102 | (number) @constant.numeric.integer 103 | 104 | ; Tokens 105 | ;------- 106 | 107 | (template_substitution 108 | "${" @punctuation.special 109 | "}" @punctuation.special) @embedded 110 | 111 | [ 112 | ";" 113 | (optional_chain) ; ?. 114 | "." 115 | "," 116 | ] @punctuation.delimiter 117 | 118 | [ 119 | "-" 120 | "--" 121 | "-=" 122 | "+" 123 | "++" 124 | "+=" 125 | "*" 126 | "*=" 127 | "**" 128 | "**=" 129 | "/" 130 | "/=" 131 | "%" 132 | "%=" 133 | "<" 134 | "<=" 135 | "<<" 136 | "<<=" 137 | "=" 138 | "==" 139 | "===" 140 | "!" 141 | "!=" 142 | "!==" 143 | "=>" 144 | ">" 145 | ">=" 146 | ">>" 147 | ">>=" 148 | ">>>" 149 | ">>>=" 150 | "~" 151 | "^" 152 | "&" 153 | "|" 154 | "^=" 155 | "&=" 156 | "|=" 157 | "&&" 158 | "||" 159 | "??" 160 | "&&=" 161 | "||=" 162 | "??=" 163 | "..." 164 | ] @operator 165 | 166 | (ternary_expression ["?" ":"] @operator) 167 | 168 | [ 169 | "(" 170 | ")" 171 | "[" 172 | "]" 173 | "{" 174 | "}" 175 | ] @punctuation.bracket 176 | 177 | [ 178 | "async" 179 | "debugger" 180 | "delete" 181 | "extends" 182 | "from" 183 | "get" 184 | "new" 185 | "set" 186 | "target" 187 | "typeof" 188 | "instanceof" 189 | "void" 190 | "with" 191 | ] @keyword 192 | 193 | [ 194 | "of" 195 | "as" 196 | "in" 197 | ] @keyword.operator 198 | 199 | [ 200 | "function" 201 | ] @keyword.function 202 | 203 | [ 204 | "class" 205 | "let" 206 | "var" 207 | ] @keyword.storage.type 208 | 209 | [ 210 | "const" 211 | "static" 212 | ] @keyword.storage.modifier 213 | 214 | [ 215 | "default" 216 | "yield" 217 | "finally" 218 | "do" 219 | "await" 220 | ] @keyword.control 221 | 222 | [ 223 | "if" 224 | "else" 225 | "switch" 226 | "case" 227 | "while" 228 | ] @keyword.control.conditional 229 | 230 | [ 231 | "for" 232 | ] @keyword.control.repeat 233 | 234 | [ 235 | "import" 236 | "export" 237 | ] @keyword.control.import 238 | 239 | [ 240 | "return" 241 | "break" 242 | "continue" 243 | ] @keyword.control.return 244 | 245 | [ 246 | "throw" 247 | "try" 248 | "catch" 249 | ] @keyword.control.exception 250 | 251 | ; jsx 252 | ; Opening elements 253 | ; ---------------- 254 | 255 | (jsx_opening_element ((identifier) @constructor 256 | (#match? @constructor "^[A-Z]"))) 257 | 258 | (jsx_opening_element (identifier) @tag) 259 | 260 | ; Closing elements 261 | ; ---------------- 262 | 263 | (jsx_closing_element ((identifier) @constructor 264 | (#match? @constructor "^[A-Z]"))) 265 | 266 | (jsx_closing_element (identifier) @tag) 267 | 268 | ; Self-closing elements 269 | ; --------------------- 270 | 271 | (jsx_self_closing_element ((identifier) @constructor 272 | (#match? @constructor "^[A-Z]"))) 273 | 274 | (jsx_self_closing_element (identifier) @tag) 275 | 276 | ; Attributes 277 | ; ---------- 278 | 279 | (jsx_attribute (property_identifier) @variable.other.member) 280 | 281 | ; Punctuation 282 | ; ----------- 283 | 284 | ; Handle attribute delimiter () 285 | (jsx_attribute "=" @punctuation.delimiter) 286 | 287 | ; 288 | (jsx_opening_element ["<" ">"] @punctuation.bracket) 289 | 290 | ; 291 | (jsx_closing_element [""] @punctuation.bracket) 292 | 293 | ; 294 | (jsx_self_closing_element ["<" "/>"] @punctuation.braket) 295 | 296 | ; javascript 297 | ; Function and method parameters 298 | ;------------------------------- 299 | 300 | ; Javascript and Typescript Treesitter grammars deviate when defining the 301 | ; tree structure for parameters, so we need to address them in each specific 302 | ; language instead of ecma. 303 | 304 | ; (p) 305 | (formal_parameters 306 | (identifier) @variable.parameter) 307 | 308 | ; (...p) 309 | (formal_parameters 310 | (rest_pattern 311 | (identifier) @variable.parameter)) 312 | 313 | ; ({ p }) 314 | (formal_parameters 315 | (object_pattern 316 | (shorthand_property_identifier_pattern) @variable.parameter)) 317 | 318 | ; ({ a: p }) 319 | (formal_parameters 320 | (object_pattern 321 | (pair_pattern 322 | value: (identifier) @variable.parameter))) 323 | 324 | ; ([ p ]) 325 | (formal_parameters 326 | (array_pattern 327 | (identifier) @variable.parameter)) 328 | 329 | ; (p = 1) 330 | (formal_parameters 331 | (assignment_pattern 332 | left: (identifier) @variable.parameter)) 333 | -------------------------------------------------------------------------------- /runtime/queries/jsx/indents.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | [ 3 | (array) 4 | (object) 5 | (arguments) 6 | (formal_parameters) 7 | 8 | (statement_block) 9 | (switch_statement) 10 | (object_pattern) 11 | (class_body) 12 | (named_imports) 13 | 14 | (binary_expression) 15 | (return_statement) 16 | (template_substitution) 17 | (export_clause) 18 | ] @indent 19 | 20 | [ 21 | (switch_case) 22 | (switch_default) 23 | ] @indent @extend 24 | 25 | [ 26 | "}" 27 | "]" 28 | ")" 29 | ] @outdent 30 | 31 | ; jsx 32 | [ 33 | (jsx_element) 34 | (jsx_self_closing_element) 35 | ] @indent 36 | 37 | (parenthesized_expression) @indent 38 | 39 | ; javascript -------------------------------------------------------------------------------- /runtime/queries/jsx/injections.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Parse the contents of tagged template literals using 3 | ; a language inferred from the tag. 4 | 5 | (call_expression 6 | function: [ 7 | (identifier) @injection.language 8 | (member_expression 9 | property: (property_identifier) @injection.language) 10 | ] 11 | arguments: (template_string) @injection.content) 12 | 13 | ; Parse the contents of gql template literals 14 | 15 | ((call_expression 16 | function: (identifier) @_template_function_name 17 | arguments: (template_string) @injection.content) 18 | (#eq? @_template_function_name "gql") 19 | (#set! injection.language "graphql")) 20 | 21 | ; Parse regex syntax within regex literals 22 | 23 | ((regex_pattern) @injection.content 24 | (#set! injection.language "regex")) 25 | 26 | ; Parse JSDoc annotations in multiline comments 27 | 28 | ((comment) @injection.content 29 | (#set! injection.language "jsdoc") 30 | (#match? @injection.content "^/\\*+")) 31 | 32 | ; Parse general tags in single line comments 33 | 34 | ((comment) @injection.content 35 | (#set! injection.language "comment") 36 | (#match? @injection.content "^//")) 37 | 38 | ; jsx 39 | 40 | ; javascript -------------------------------------------------------------------------------- /runtime/queries/jsx/locals.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Scopes 3 | ;------- 4 | 5 | [ 6 | (statement_block) 7 | (function) 8 | (arrow_function) 9 | (function_declaration) 10 | (method_definition) 11 | ] @local.scope 12 | 13 | ; Definitions 14 | ;------------ 15 | 16 | ; ...i 17 | (rest_pattern 18 | (identifier) @local.definition) 19 | 20 | ; { i } 21 | (object_pattern 22 | (shorthand_property_identifier_pattern) @local.definition) 23 | 24 | ; { a: i } 25 | (object_pattern 26 | (pair_pattern 27 | value: (identifier) @local.definition)) 28 | 29 | ; [ i ] 30 | (array_pattern 31 | (identifier) @local.definition) 32 | 33 | ; i => ... 34 | (arrow_function 35 | parameter: (identifier) @local.definition) 36 | 37 | ; const/let/var i = ... 38 | (variable_declarator 39 | name: (identifier) @local.definition) 40 | 41 | ; References 42 | ;------------ 43 | 44 | (identifier) @local.reference 45 | 46 | ; jsx 47 | 48 | ; javascript 49 | ; Definitions 50 | ;------------ 51 | ; Javascript and Typescript Treesitter grammars deviate when defining the 52 | ; tree structure for parameters, so we need to address them in each specific 53 | ; language instead of ecma. 54 | 55 | ; (i) 56 | (formal_parameters 57 | (identifier) @local.definition) 58 | 59 | ; (i = 1) 60 | (formal_parameters 61 | (assignment_pattern 62 | left: (identifier) @local.definition)) 63 | -------------------------------------------------------------------------------- /runtime/queries/jsx/tags.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | 3 | ; jsx 4 | 5 | ; javascript 6 | ( 7 | (comment)* @doc 8 | . 9 | (method_definition 10 | name: (property_identifier) @name) @definition.method 11 | (#not-eq? @name "constructor") 12 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 13 | (#select-adjacent! @doc @definition.method) 14 | ) 15 | 16 | ( 17 | (comment)* @doc 18 | . 19 | [ 20 | (class 21 | name: (_) @name) 22 | (class_declaration 23 | name: (_) @name) 24 | ] @definition.class 25 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 26 | (#select-adjacent! @doc @definition.class) 27 | ) 28 | 29 | ( 30 | (comment)* @doc 31 | . 32 | [ 33 | (function 34 | name: (identifier) @name) 35 | (function_declaration 36 | name: (identifier) @name) 37 | (generator_function 38 | name: (identifier) @name) 39 | (generator_function_declaration 40 | name: (identifier) @name) 41 | ] @definition.function 42 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 43 | (#select-adjacent! @doc @definition.function) 44 | ) 45 | 46 | ( 47 | (comment)* @doc 48 | . 49 | (lexical_declaration 50 | (variable_declarator 51 | name: (identifier) @name 52 | value: [(arrow_function) (function)]) @definition.function) 53 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 54 | (#select-adjacent! @doc @definition.function) 55 | ) 56 | 57 | ( 58 | (comment)* @doc 59 | . 60 | (variable_declaration 61 | (variable_declarator 62 | name: (identifier) @name 63 | value: [(arrow_function) (function)]) @definition.function) 64 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") 65 | (#select-adjacent! @doc @definition.function) 66 | ) 67 | 68 | (assignment_expression 69 | left: [ 70 | (identifier) @name 71 | (member_expression 72 | property: (property_identifier) @name) 73 | ] 74 | right: [(arrow_function) (function)] 75 | ) @definition.function 76 | 77 | (pair 78 | key: (property_identifier) @name 79 | value: [(arrow_function) (function)]) @definition.function 80 | 81 | ( 82 | (call_expression 83 | function: (identifier) @name) @reference.call 84 | (#not-match? @name "^(require)$") 85 | ) 86 | 87 | (call_expression 88 | function: (member_expression 89 | property: (property_identifier) @name) 90 | arguments: (_) @reference.call) 91 | 92 | (new_expression 93 | constructor: (_) @name) @reference.class -------------------------------------------------------------------------------- /runtime/queries/jsx/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | (function_declaration 3 | body: (_) @function.inside) @function.around 4 | 5 | (function 6 | body: (_) @function.inside) @function.around 7 | 8 | (arrow_function 9 | body: (_) @function.inside) @function.around 10 | 11 | (method_definition 12 | body: (_) @function.inside) @function.around 13 | 14 | (generator_function_declaration 15 | body: (_) @function.inside) @function.around 16 | 17 | (class_declaration 18 | body: (class_body) @class.inside) @class.around 19 | 20 | (class 21 | (class_body) @class.inside) @class.around 22 | 23 | (export_statement 24 | declaration: [ 25 | (function_declaration) @function.around 26 | (class_declaration) @class.around 27 | ]) 28 | 29 | (formal_parameters 30 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 31 | 32 | (arguments 33 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 34 | 35 | (comment) @comment.inside 36 | 37 | (comment)+ @comment.around 38 | 39 | ; jsx 40 | 41 | ; javascript 42 | -------------------------------------------------------------------------------- /runtime/queries/markdown/README.md: -------------------------------------------------------------------------------- 1 | All the files in this directory were taken and adapted from 2 | [Helix Markdown queries](https://github.com/helix-editor/helix/tree/master/runtime/queries/markdown) and are then 3 | licensed under the [Mozilla Public License 2.0](https://github.com/helix-editor/helix/blob/master/LICENSE). 4 | -------------------------------------------------------------------------------- /runtime/queries/markdown/highlights.scm: -------------------------------------------------------------------------------- 1 | ; kak-tree-sitter notes: taken from helix-editor/helix 2 | 3 | (setext_heading (paragraph) @markup.heading.1 (setext_h1_underline) @markup.heading.marker) 4 | (setext_heading (paragraph) @markup.heading.2 (setext_h2_underline) @markup.heading.marker) 5 | 6 | (atx_heading (atx_h1_marker) @markup.heading.marker (inline) @markup.heading.1) 7 | (atx_heading (atx_h2_marker) @markup.heading.marker (inline) @markup.heading.2) 8 | (atx_heading (atx_h3_marker) @markup.heading.marker (inline) @markup.heading.3) 9 | (atx_heading (atx_h4_marker) @markup.heading.marker (inline) @markup.heading.4) 10 | (atx_heading (atx_h5_marker) @markup.heading.marker (inline) @markup.heading.5) 11 | (atx_heading (atx_h6_marker) @markup.heading.marker (inline) @markup.heading.6) 12 | 13 | [ 14 | (indented_code_block) 15 | (fenced_code_block) 16 | ] @markup.raw.block 17 | 18 | (info_string) @label 19 | 20 | [ 21 | (fenced_code_block_delimiter) 22 | ] @punctuation.bracket 23 | 24 | [ 25 | (link_destination) 26 | ] @markup.link.url 27 | 28 | [ 29 | (link_label) 30 | ] @markup.link.label 31 | 32 | [ 33 | (list_marker_plus) 34 | (list_marker_minus) 35 | (list_marker_star) 36 | ] @markup.list.unnumbered 37 | 38 | [ 39 | (list_marker_dot) 40 | (list_marker_parenthesis) 41 | ] @markup.list.numbered 42 | 43 | (task_list_marker_checked) @markup.list.checked 44 | (task_list_marker_unchecked) @markup.list.unchecked 45 | 46 | (thematic_break) @punctuation.special 47 | 48 | [ 49 | (block_continuation) 50 | (block_quote_marker) 51 | ] @punctuation.special 52 | 53 | [ 54 | (backslash_escape) 55 | ] @string.escape 56 | 57 | (block_quote) @markup.quote 58 | 59 | (pipe_table_row 60 | "|" @punctuation.special) 61 | (pipe_table_header 62 | "|" @punctuation.special) 63 | (pipe_table_delimiter_row) @punctuation.special 64 | -------------------------------------------------------------------------------- /runtime/queries/markdown/injections.scm: -------------------------------------------------------------------------------- 1 | ; kak-tree-sitter notes: taken from helix-editor/helix 2 | 3 | ; From nvim-treesitter/nvim-treesitter 4 | 5 | (fenced_code_block 6 | (code_fence_content) @injection.shebang @injection.content 7 | (#set! injection.include-children)) 8 | 9 | (fenced_code_block 10 | (info_string 11 | (language) @injection.language) 12 | (code_fence_content) @injection.content (#set! injection.include-children)) 13 | 14 | ((html_block) @injection.content 15 | (#set! injection.language "html") 16 | (#set! injection.include-children) 17 | (#set! injection.combined)) 18 | 19 | ((pipe_table_cell) @injection.content (#set! injection.language "markdown.inline") (#set! injection.include-children)) 20 | 21 | ((minus_metadata) @injection.content (#set! injection.language "yaml") (#set! injection.include-children)) 22 | ((plus_metadata) @injection.content (#set! injection.language "toml") (#set! injection.include-children)) 23 | 24 | ((inline) @injection.content (#set! injection.language "markdown.inline") (#set! injection.include-children)) 25 | -------------------------------------------------------------------------------- /runtime/queries/tsx/README.md: -------------------------------------------------------------------------------- 1 | All the files in this directory were taken and adapted from 2 | [Helix TSX queries](https://github.com/helix-editor/helix/tree/master/runtime/queries/tsx) and are then 3 | licensed under the [Mozilla Public License 2.0](https://github.com/helix-editor/helix/blob/master/LICENSE). 4 | -------------------------------------------------------------------------------- /runtime/queries/tsx/indents.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | [ 3 | (array) 4 | (object) 5 | (arguments) 6 | (formal_parameters) 7 | 8 | (statement_block) 9 | (switch_statement) 10 | (object_pattern) 11 | (class_body) 12 | (named_imports) 13 | 14 | (binary_expression) 15 | (return_statement) 16 | (template_substitution) 17 | (export_clause) 18 | ] @indent 19 | 20 | [ 21 | (switch_case) 22 | (switch_default) 23 | ] @indent @extend 24 | 25 | [ 26 | "}" 27 | "]" 28 | ")" 29 | ] @outdent 30 | 31 | ; jsx 32 | [ 33 | (jsx_element) 34 | (jsx_self_closing_element) 35 | ] @indent 36 | 37 | (parenthesized_expression) @indent 38 | 39 | ; typescript 40 | [ 41 | (enum_declaration) 42 | (interface_declaration) 43 | (object_type) 44 | ] @indent 45 | -------------------------------------------------------------------------------- /runtime/queries/tsx/injections.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Parse the contents of tagged template literals using 3 | ; a language inferred from the tag. 4 | 5 | (call_expression 6 | function: [ 7 | (identifier) @injection.language 8 | (member_expression 9 | property: (property_identifier) @injection.language) 10 | ] 11 | arguments: (template_string) @injection.content) 12 | 13 | ; Parse the contents of gql template literals 14 | 15 | ((call_expression 16 | function: (identifier) @_template_function_name 17 | arguments: (template_string) @injection.content) 18 | (#eq? @_template_function_name "gql") 19 | (#set! injection.language "graphql")) 20 | 21 | ; Parse regex syntax within regex literals 22 | 23 | ((regex_pattern) @injection.content 24 | (#set! injection.language "regex")) 25 | 26 | ; Parse JSDoc annotations in multiline comments 27 | 28 | ((comment) @injection.content 29 | (#set! injection.language "jsdoc") 30 | (#match? @injection.content "^/\\*+")) 31 | 32 | ; Parse general tags in single line comments 33 | 34 | ((comment) @injection.content 35 | (#set! injection.language "comment") 36 | (#match? @injection.content "^//")) 37 | 38 | ; jsx 39 | 40 | ; typescript -------------------------------------------------------------------------------- /runtime/queries/tsx/locals.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Scopes 3 | ;------- 4 | 5 | [ 6 | (statement_block) 7 | (function) 8 | (arrow_function) 9 | (function_declaration) 10 | (method_definition) 11 | ] @local.scope 12 | 13 | ; Definitions 14 | ;------------ 15 | 16 | ; ...i 17 | (rest_pattern 18 | (identifier) @local.definition) 19 | 20 | ; { i } 21 | (object_pattern 22 | (shorthand_property_identifier_pattern) @local.definition) 23 | 24 | ; { a: i } 25 | (object_pattern 26 | (pair_pattern 27 | value: (identifier) @local.definition)) 28 | 29 | ; [ i ] 30 | (array_pattern 31 | (identifier) @local.definition) 32 | 33 | ; i => ... 34 | (arrow_function 35 | parameter: (identifier) @local.definition) 36 | 37 | ; const/let/var i = ... 38 | (variable_declarator 39 | name: (identifier) @local.definition) 40 | 41 | ; References 42 | ;------------ 43 | 44 | (identifier) @local.reference 45 | 46 | ; jsx 47 | 48 | ; typescript 49 | ; Definitions 50 | ;------------ 51 | 52 | ; Javascript and Typescript Treesitter grammars deviate when defining the 53 | ; tree structure for parameters, so we need to address them in each specific 54 | ; language instead of ecma. 55 | 56 | ; (i: t) 57 | ; (i: t = 1) 58 | (required_parameter 59 | (identifier) @local.definition) 60 | 61 | ; (i?: t) 62 | ; (i?: t = 1) // Invalid but still posible to hihglight. 63 | (optional_parameter 64 | (identifier) @local.definition) -------------------------------------------------------------------------------- /runtime/queries/tsx/tags.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | 3 | ; jsx 4 | 5 | ; typescript 6 | ; Definitions 7 | ;------------ 8 | 9 | ; Javascript and Typescript Treesitter grammars deviate when defining the 10 | ; tree structure for parameters, so we need to address them in each specific 11 | ; language instead of ecma. 12 | 13 | ; (i: t) 14 | ; (i: t = 1) 15 | (required_parameter 16 | (identifier) @local.definition) 17 | 18 | ; (i?: t) 19 | ; (i?: t = 1) // Invalid but still posible to hihglight. 20 | (optional_parameter 21 | (identifier) @local.definition) -------------------------------------------------------------------------------- /runtime/queries/tsx/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | (function_declaration 3 | body: (_) @function.inside) @function.around 4 | 5 | (function 6 | body: (_) @function.inside) @function.around 7 | 8 | (arrow_function 9 | body: (_) @function.inside) @function.around 10 | 11 | (method_definition 12 | body: (_) @function.inside) @function.around 13 | 14 | (generator_function_declaration 15 | body: (_) @function.inside) @function.around 16 | 17 | (class_declaration 18 | body: (class_body) @class.inside) @class.around 19 | 20 | (class 21 | (class_body) @class.inside) @class.around 22 | 23 | (export_statement 24 | declaration: [ 25 | (function_declaration) @function.around 26 | (class_declaration) @class.around 27 | ]) 28 | 29 | (formal_parameters 30 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 31 | 32 | (arguments 33 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 34 | 35 | (comment) @comment.inside 36 | 37 | (comment)+ @comment.around 38 | 39 | ; jsx 40 | 41 | ; typescript 42 | [ 43 | (interface_declaration 44 | body:(_) @class.inside) 45 | (type_alias_declaration 46 | value: (_) @class.inside) 47 | ] @class.around 48 | -------------------------------------------------------------------------------- /runtime/queries/typescript/README.md: -------------------------------------------------------------------------------- 1 | All the files in this directory were taken and adapted from 2 | [Helix Typescript queries](https://github.com/helix-editor/helix/tree/master/runtime/queries/typescript) and are then 3 | licensed under the [Mozilla Public License 2.0](https://github.com/helix-editor/helix/blob/master/LICENSE). 4 | -------------------------------------------------------------------------------- /runtime/queries/typescript/indents.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | [ 3 | (array) 4 | (object) 5 | (arguments) 6 | (formal_parameters) 7 | 8 | (statement_block) 9 | (switch_statement) 10 | (object_pattern) 11 | (class_body) 12 | (named_imports) 13 | 14 | (binary_expression) 15 | (return_statement) 16 | (template_substitution) 17 | (export_clause) 18 | ] @indent 19 | 20 | [ 21 | (switch_case) 22 | (switch_default) 23 | ] @indent @extend 24 | 25 | [ 26 | "}" 27 | "]" 28 | ")" 29 | ] @outdent 30 | 31 | ; typescript 32 | ; Parse the contents of tagged template literals using 33 | ; a language inferred from the tag. 34 | 35 | (call_expression 36 | function: [ 37 | (identifier) @injection.language 38 | (member_expression 39 | property: (property_identifier) @injection.language) 40 | ] 41 | arguments: (template_string) @injection.content) 42 | 43 | ; Parse the contents of gql template literals 44 | 45 | ((call_expression 46 | function: (identifier) @_template_function_name 47 | arguments: (template_string) @injection.content) 48 | (#eq? @_template_function_name "gql") 49 | (#set! injection.language "graphql")) 50 | 51 | ; Parse regex syntax within regex literals 52 | 53 | ((regex_pattern) @injection.content 54 | (#set! injection.language "regex")) 55 | 56 | ; Parse JSDoc annotations in multiline comments 57 | 58 | ((comment) @injection.content 59 | (#set! injection.language "jsdoc") 60 | (#match? @injection.content "^/\\*+")) 61 | 62 | ; Parse general tags in single line comments 63 | 64 | ((comment) @injection.content 65 | (#set! injection.language "comment") 66 | (#match? @injection.content "^//")) -------------------------------------------------------------------------------- /runtime/queries/typescript/injections.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Parse the contents of tagged template literals using 3 | ; a language inferred from the tag. 4 | 5 | (call_expression 6 | function: [ 7 | (identifier) @injection.language 8 | (member_expression 9 | property: (property_identifier) @injection.language) 10 | ] 11 | arguments: (template_string) @injection.content) 12 | 13 | ; Parse the contents of gql template literals 14 | 15 | ((call_expression 16 | function: (identifier) @_template_function_name 17 | arguments: (template_string) @injection.content) 18 | (#eq? @_template_function_name "gql") 19 | (#set! injection.language "graphql")) 20 | 21 | ; Parse regex syntax within regex literals 22 | 23 | ((regex_pattern) @injection.content 24 | (#set! injection.language "regex")) 25 | 26 | ; Parse JSDoc annotations in multiline comments 27 | 28 | ((comment) @injection.content 29 | (#set! injection.language "jsdoc") 30 | (#match? @injection.content "^/\\*+")) 31 | 32 | ; Parse general tags in single line comments 33 | 34 | ((comment) @injection.content 35 | (#set! injection.language "comment") 36 | (#match? @injection.content "^//")) 37 | 38 | ; typescript -------------------------------------------------------------------------------- /runtime/queries/typescript/locals.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | ; Scopes 3 | ;------- 4 | 5 | [ 6 | (statement_block) 7 | (function) 8 | (arrow_function) 9 | (function_declaration) 10 | (method_definition) 11 | ] @local.scope 12 | 13 | ; Definitions 14 | ;------------ 15 | 16 | ; ...i 17 | (rest_pattern 18 | (identifier) @local.definition) 19 | 20 | ; { i } 21 | (object_pattern 22 | (shorthand_property_identifier_pattern) @local.definition) 23 | 24 | ; { a: i } 25 | (object_pattern 26 | (pair_pattern 27 | value: (identifier) @local.definition)) 28 | 29 | ; [ i ] 30 | (array_pattern 31 | (identifier) @local.definition) 32 | 33 | ; i => ... 34 | (arrow_function 35 | parameter: (identifier) @local.definition) 36 | 37 | ; const/let/var i = ... 38 | (variable_declarator 39 | name: (identifier) @local.definition) 40 | 41 | ; References 42 | ;------------ 43 | 44 | (identifier) @local.reference 45 | 46 | ; typescript 47 | ; Definitions 48 | ;------------ 49 | 50 | ; Javascript and Typescript Treesitter grammars deviate when defining the 51 | ; tree structure for parameters, so we need to address them in each specific 52 | ; language instead of ecma. 53 | 54 | ; (i: t) 55 | ; (i: t = 1) 56 | (required_parameter 57 | (identifier) @local.definition) 58 | 59 | ; (i?: t) 60 | ; (i?: t = 1) // Invalid but still posible to hihglight. 61 | (optional_parameter 62 | (identifier) @local.definition) -------------------------------------------------------------------------------- /runtime/queries/typescript/tags.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | 3 | ; typescript 4 | (function_declaration 5 | body: (_) @function.inside) @function.around 6 | 7 | (function 8 | body: (_) @function.inside) @function.around 9 | 10 | (arrow_function 11 | body: (_) @function.inside) @function.around 12 | 13 | (method_definition 14 | body: (_) @function.inside) @function.around 15 | 16 | (generator_function_declaration 17 | body: (_) @function.inside) @function.around 18 | 19 | (class_declaration 20 | body: (class_body) @class.inside) @class.around 21 | 22 | (class 23 | (class_body) @class.inside) @class.around 24 | 25 | (export_statement 26 | declaration: [ 27 | (function_declaration) @function.around 28 | (class_declaration) @class.around 29 | ]) 30 | 31 | (formal_parameters 32 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 33 | 34 | (arguments 35 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 36 | 37 | (comment) @comment.inside 38 | 39 | (comment)+ @comment.around 40 | -------------------------------------------------------------------------------- /runtime/queries/typescript/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; ecma 2 | (function_declaration 3 | body: (_) @function.inside) @function.around 4 | 5 | (function 6 | body: (_) @function.inside) @function.around 7 | 8 | (arrow_function 9 | body: (_) @function.inside) @function.around 10 | 11 | (method_definition 12 | body: (_) @function.inside) @function.around 13 | 14 | (generator_function_declaration 15 | body: (_) @function.inside) @function.around 16 | 17 | (class_declaration 18 | body: (class_body) @class.inside) @class.around 19 | 20 | (class 21 | (class_body) @class.inside) @class.around 22 | 23 | (export_statement 24 | declaration: [ 25 | (function_declaration) @function.around 26 | (class_declaration) @class.around 27 | ]) 28 | 29 | (formal_parameters 30 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 31 | 32 | (arguments 33 | ((_) @parameter.inside . ","? @parameter.around) @parameter.around) 34 | 35 | (comment) @comment.inside 36 | 37 | (comment)+ @comment.around 38 | 39 | ; typescript 40 | [ 41 | (interface_declaration 42 | body:(_) @class.inside) 43 | (type_alias_declaration 44 | value: (_) @class.inside) 45 | ] @class.around 46 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | 3 | fn_params_layout = "Tall" 4 | force_explicit_abi = true 5 | hard_tabs = false 6 | max_width = 100 7 | merge_derives = true 8 | newline_style = "Unix" 9 | remove_nested_parens = true 10 | reorder_imports = true 11 | reorder_modules = true 12 | tab_spaces = 2 13 | use_field_init_shorthand = true 14 | use_small_heuristics = "Default" 15 | use_try_shorthand = true 16 | --------------------------------------------------------------------------------