feat!: track parser revision in Lua

Problem: Tracking parser revision in lockfile and allowing override
through the parsers module complicates the code. In addition, only
revision changes are handled robustly, not changes to other installation
info.

Solution: Track parser revision in the parsers module directly. Reload
parser table on every install or update call. Support modifying parser
table in a `User TSUpdate` autocommand.
This commit is contained in:
Christian Clason 2024-04-14 16:25:28 +02:00
parent 054080bf59
commit c17de56890
21 changed files with 1007 additions and 995 deletions

View file

@ -7,6 +7,7 @@ on:
pull_request:
branches:
- "main"
workflow_dispatch:
# Cancel any in-progress CI runs for a PR if it is updated
concurrency:

View file

@ -1,55 +0,0 @@
name: Update lockfile
on:
schedule:
- cron: "30 6 * * 6"
workflow_dispatch:
jobs:
update-lockfile:
name: Update lockfile
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.TOKEN_ID }}
private-key: ${{ secrets.TOKEN_PRIVATE_KEY }}
- name: Prepare
env:
NVIM_TAG: nightly
run: |
wget https://github.com/josephburnett/jd/releases/download/v1.7.1/jd-amd64-linux
mv jd-amd64-linux /tmp/jd
chmod +x /tmp/jd
bash scripts/ci-install.sh
- name: Update parsers
env:
SKIP_LOCKFILE_UPDATE_FOR_LANGS: "bp,devicetree,dhall,elm,enforce,git_config,nickel,rescript,rust,slint,sql,t32,templ,typespec,verilog,wit"
run: |
cp lockfile.json /tmp/old_lockfile.json
nvim -l ./scripts/update-lockfile.lua
UPDATED_PARSERS=$(/tmp/jd -f merge /tmp/old_lockfile.json lockfile.json | jq -r 'keys | join(", ")')
echo "UPDATED_PARSERS=$UPDATED_PARSERS" >> $GITHUB_ENV
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ steps.app-token.outputs.token }}
sign-commits: true
commit-message: "bot(lockfile): update ${{ env.UPDATED_PARSERS }}"
title: "Update lockfile.json: ${{ env.UPDATED_PARSERS }}"
body: "[beep boop](https://github.com/peter-evans/create-pull-request)"
branch: update-lockfile-pr
base: ${{ github.head_ref }}
- name: Enable Pull Request Automerge
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh pr merge --rebase --auto update-lockfile-pr

55
.github/workflows/update-parsers.yml vendored Normal file
View file

@ -0,0 +1,55 @@
name: Update parsers
on:
schedule:
- cron: "30 6 * * *"
workflow_dispatch:
env:
BIN_DIR: ${{ github.workspace }}/bin
jobs:
update-parsers:
name: Update parsers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.TOKEN_ID }}
private-key: ${{ secrets.TOKEN_PRIVATE_KEY }}
- name: Add $BIN_DIR to PATH
run: echo "$BIN_DIR" >> $GITHUB_PATH
- name: Prepare
env:
NVIM_TAG: nightly
run: |
bash scripts/ci-install.sh
wget --directory-prefix="$BIN_DIR" https://github.com/JohnnyMorganz/StyLua/releases/latest/download/stylua-linux-x86_64.zip
(cd "$BIN_DIR"; unzip stylua*.zip)
- name: Update parsers
run: ./scripts/update-parsers.lua
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
add-paths: lua/nvim-treesitter/parsers.lua
token: ${{ steps.app-token.outputs.token }}
sign-commits: true
commit-message: "bot(parsers): update ${{ env.UPDATED_PARSERS }}"
title: "Update parsers: ${{ env.UPDATED_PARSERS }}"
body: "[beep boop](https://github.com/peter-evans/create-pull-request)"
branch: update-parsers-pr
base: ${{ github.head_ref }}
- name: Enable Pull Request Automerge
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: gh pr merge --rebase --auto update-parsers-pr

View file

@ -32,10 +32,11 @@ jobs:
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
add-paths: SUPPORTED_LANGUAGES.md
token: ${{ steps.app-token.outputs.token }}
sign-commits: true
commit-message: "bot(readme): update"
title: Update README
title: Update SUPPORTED_LANGUAGES.md
body: "[beep boop](https://github.com/peter-evans/create-pull-request)"
branch: update-readme-pr
base: ${{ github.head_ref }}

View file

@ -13,17 +13,17 @@ Depending on which part of the plugin you want to contribute to, please read the
To add a new parser, edit the following files:
1. In `lua/parsers.lua`, add an entry to the `M.configs` table of the following form:
1. In `lua/parsers.lua`, add an entry to the returned table of the following form:
```lua
zimbu = {
install_info = {
url = 'https://github.com/zimbulang/tree-sitter-zimbu', -- local path or git repo
url = 'https://github.com/zimbulang/tree-sitter-zimbu', -- git repo; use `path` for local path
files = { 'src/parser.c' }, -- note that some parsers also require src/scanner.c
-- optional entries:
branch = 'develop', -- only needed if different from default branch
location = 'parser', -- only needed if the parser is in subdirectory of a "monorepo"
revision = 'v2.1', -- tag or commit hash; bypasses automated updates
revision = 'v2.1', -- tag or commit hash, will be updated automatically
generate = true, -- only needed if repo does not contain pre-generated src/parser.c
generate_from_json = true, -- only needed if grammar.js has npm-installed dependencies
},
@ -31,21 +31,13 @@ zimbu = {
tier = 3, -- community-contributed parser
-- optional entries:
requires = { 'vim' }, -- if the queries inherit from another language
readme_note = "an example language", -- if the
readme_note = "an example language",
}
```
**Note:** The "maintainers" here refers to the person maintaining the **queries** in `nvim-treesitter`, not the parser maintainers (who likely don't use Neovim). The maintainers' duty is to review issues and PRs related to the query and to keep them updated with respect to parser changes.
2. In `lockfile.json`, add an entry for the current commit your queries are compatible with:
```json
"zimbu": {
"revision": "0d08703e4c3f426ec61695d7617415fff97029bd"
},
```
3. If the parser name is not the same as the Vim filetype, add an entry to the `filetypes` table in `plugin/filetypes.lua`:
2. If the parser name is not the same as the Vim filetype, add an entry to the `filetypes` table in `plugin/filetypes.lua`:
```lua
zimbu = { 'zu' },

View file

@ -126,28 +126,40 @@ These queries can be used to look up definitions and references to identifiers i
If you have a parser that is not on the list of supported languages (either as a repository on Github or in a local directory), you can add it manually for use by `nvim-treesitter` as follows:
1. Clone the repository or [create a new project](https://tree-sitter.github.io/tree-sitter/creating-parsers#project-setup) in, say, `~/projects/tree-sitter-zimbu`. Make sure that the `tree-sitter-cli` executable is installed and in your path; see <https://tree-sitter.github.io/tree-sitter/creating-parsers#installation> for installation instructions.
2. Run `tree-sitter generate` in this directory (followed by `tree-sitter test` for good measure).
3. Add the following snippet to your `init.lua`:
1. Add the following snippet in a `User TSUpdate` autocommand:
```lua
local parser_config = require('nvim-treesitter.parsers').configs
parser_config.zimbu = {
vim.api.nvim_create_autocmd('User', { pattern = 'TSUpdate',
callback = function()
require('nvim-treesitter.parsers').zimbu = {
install_info = {
url = '~/projects/tree-sitter-zimbu', -- local path or git repo
url = 'https://github.com/zimbulang/tree-sitter-zimbu',
files = { 'src/parser.c' }, -- note that some parsers also require src/scanner.c
revision = <sha>, -- commit hash for revision to check out; HEAD if missing
-- optional entries:
branch = 'develop', -- only needed if different from default branch
location= 'parser', -- only needed if the parser is in subdirectory of a "monorepo"
location = 'parser', -- only needed if the parser is in subdirectory of a "monorepo"
generate = true, -- only needed if repo does not contain pre-generated src/parser.c
generate_from_json = true, -- only needed if parser has npm dependencies
},
}
}
end})
```
If you use a git repository for your parser and want to use a specific version, you can set the `revision` key
in the `install_info` table for you parser config.
4. If the parser name differs from the filetype(s) used by Neovim, you need to register the parser via
Alternatively, if you have a local checkout, you can instead use
```lua
install_info = {
path = '~/parsers/tree-sitter-zimbu',
files = { 'src/parser.c' }, -- note that some parsers also require src/scanner.c
-- optional entries
location = 'parser', -- only needed if the parser is in subdirectory of a "monorepo"
generate = true, -- only needed if repo does not contain pre-generated src/parser.c
generate_from_json = true, -- only needed if parser has npm dependencies
},
```
This will always use the state of the directory as-is (i.e., `branch` and `revision` will be ignored).
2. If the parser name differs from the filetype(s) used by Neovim, you need to register the parser via
```lua
vim.treesitter.language.register('zimbu', { 'zu' })
@ -155,13 +167,20 @@ vim.treesitter.language.register('zimbu', { 'zu' })
If Neovim does not detect your language's filetype by default, you can use [Neovim's `vim.filetype.add()`](<https://neovim.io/doc/user/lua.html#vim.filetype.add()>) to add a custom detection rule.
5. Start `nvim` and `:TSInstall zimbu`.
You can also skip step 2 and use `:TSInstallFromGrammar zimbu` to install directly from a `grammar.js` in the top-level directory specified by `url`.
Once the parser is installed, you can update it (from the latest revision of the `main` branch if `url` is a Github repository) with `:TSUpdate zimbu`.
3. Start `nvim` and `:TSInstall zimbu`.
**Note:** Parsers using external scanner need to be written in C. C++ scanners are no longer supported.
### Modifying parsers
You can use the same approach for overriding parser information. E.g., if you always want to generate the `lua` parser from grammar, add
```lua
vim.api.nvim_create_autocmd('User', { pattern = 'TSUpdate',
callback = function()
require('nvim-treesitter.parsers').lua.install_info.generate = true
end})
```
## Adding queries
Queries can be placed anywhere in your `runtimepath` under `queries/<language>`, with earlier directories taking precedence unless the queries are marked with `; extends`; see `:h treesitter-query`.
@ -231,7 +250,7 @@ require("nvim-treesitter.install").prefer_git = true
In your Lua config:
```lua
for _, config in pairs(require("nvim-treesitter.parsers").configs) do
for _, config in pairs(require("nvim-treesitter.parsers")) do
config.install_info.url = config.install_info.url:gsub("https://github.com/", "something else")
end

View file

@ -26,9 +26,9 @@ Language | Tier | Queries | CLI | Maintainer
[bibtex](https://github.com/latex-lsp/tree-sitter-bibtex) | community | `HFI  ` | | @theHamsta, @clason
[bicep](https://github.com/tree-sitter-grammars/tree-sitter-bicep) | core | `HFIJL` | | @amaanq
[bitbake](https://github.com/tree-sitter-grammars/tree-sitter-bitbake) | core | `HFIJL` | | @amaanq
[blueprint](https://gitlab.com/gabmus/tree-sitter-blueprint.git) | unsupported | `H    ` | | @gabmus
[blueprint](https://gitlab.com/gabmus/tree-sitter-blueprint) | unsupported | `H    ` | | @gabmus
[c](https://github.com/tree-sitter/tree-sitter-c) | stable | `HFIJL` | | @amaanq
[c_sharp](https://github.com/tree-sitter/tree-sitter-c-sharp) | core | `HF JL` | | @Luxed
[c_sharp](https://github.com/tree-sitter/tree-sitter-c-sharp) | core | `HF JL` | | @amaanq
[cairo](https://github.com/tree-sitter-grammars/tree-sitter-cairo) | core | `HFIJL` | | @amaanq
[capnp](https://github.com/tree-sitter-grammars/tree-sitter-capnp) | core | `HFIJL` | | @amaanq
[chatito](https://github.com/tree-sitter-grammars/tree-sitter-chatito) | core | `HFIJL` | | @ObserverOfTime
@ -77,7 +77,7 @@ ecma (queries only)[^ecma] | community | `HFIJL` | | @steelsojka
[fortran](https://github.com/stadelmanma/tree-sitter-fortran) | community | `HFI  ` | | @amaanq
[fsh](https://github.com/mgramigna/tree-sitter-fsh) | community | `H    ` | | @mgramigna
[func](https://github.com/tree-sitter-grammars/tree-sitter-func) | core | `H    ` | | @amaanq
[fusion](https://gitlab.com/jirgn/tree-sitter-fusion.git) | community | `HFI L` | | @jirgn
[fusion](https://gitlab.com/jirgn/tree-sitter-fusion) | community | `HFI L` | | @jirgn
[gdscript](https://github.com/PrestonKnopp/tree-sitter-gdscript)[^gdscript] | community | `HFIJL` | | @PrestonKnopp
[gdshader](https://github.com/GodOfAvacyn/tree-sitter-gdshader) | community | `H  J ` | | @godofavacyn
[git_config](https://github.com/the-mikedavis/tree-sitter-git-config) | community | `HF J ` | | @amaanq
@ -102,7 +102,7 @@ ecma (queries only)[^ecma] | community | `HFIJL` | | @steelsojka
[gstlaunch](https://github.com/tree-sitter-grammars/tree-sitter-gstlaunch) | core | `H    ` | | @theHamsta
[hack](https://github.com/slackhq/tree-sitter-hack) | unsupported | `H    ` | |
[hare](https://github.com/tree-sitter-grammars/tree-sitter-hare) | core | `HFIJL` | | @amaanq
[haskell](https://github.com/tree-sitter/tree-sitter-haskell) | core | `HF J ` | | @mrcjkb
[haskell](https://github.com/tree-sitter/tree-sitter-haskell) | core | `HF JL` | | @mrcjkb
[haskell_persistent](https://github.com/MercuryTechnologies/tree-sitter-haskell-persistent) | community | `HF   ` | | @lykahb
[hcl](https://github.com/tree-sitter-grammars/tree-sitter-hcl) | core | `HFIJ ` | | @MichaHoffmann
[heex](https://github.com/connorlay/tree-sitter-heex) | community | `HFIJL` | | @connorlay
@ -129,7 +129,7 @@ html_tags (queries only)[^html_tags] | community | `H IJ ` | | @TravonteD
[jsdoc](https://github.com/tree-sitter/tree-sitter-jsdoc) | core | `H    ` | | @steelsojka
[json](https://github.com/tree-sitter/tree-sitter-json) | core | `HFI L` | | @steelsojka
[json5](https://github.com/Joakker/tree-sitter-json5) | community | `H  J ` | | @Joakker
[jsonc](https://gitlab.com/WhyNotHugo/tree-sitter-jsonc.git) | community | `HFIJL` | | @WhyNotHugo
[jsonc](https://gitlab.com/WhyNotHugo/tree-sitter-jsonc) | community | `HFIJL` | | @WhyNotHugo
[jsonnet](https://github.com/sourcegraph/tree-sitter-jsonnet) | community | `HF  L` | | @nawordar
jsx (queries only)[^jsx] | community | `HFIJ ` | | @steelsojka
[julia](https://github.com/tree-sitter/tree-sitter-julia) | core | `HFIJL` | | @theHamsta
@ -139,7 +139,7 @@ jsx (queries only)[^jsx] | community | `HFIJ ` | | @steelsojka
[kotlin](https://github.com/fwcd/tree-sitter-kotlin) | community | `HF JL` | | @SalBakraa
[koto](https://github.com/koto-lang/tree-sitter-koto) | community | `HF JL` | | @irh
[kusto](https://github.com/Willem-J-an/tree-sitter-kusto) | community | `H  J ` | | @Willem-J-an
[lalrpop](https://github.com/traxys/tree-sitter-lalrpop) | community | `H  JL` | | @traxys
[lalrpop](https://github.com/traxys/tree-sitter-lalrpop) | community | `HF JL` | | @traxys
[latex](https://github.com/latex-lsp/tree-sitter-latex) | community | `HF J ` | ✓ | @theHamsta, @clason
[ledger](https://github.com/cbarrete/tree-sitter-ledger) | community | `HFIJ ` | | @cbarrete
[leo](https://github.com/r001/tree-sitter-leo) | community | `H IJ ` | | @r001
@ -175,7 +175,7 @@ jsx (queries only)[^jsx] | community | `HFIJ ` | | @steelsojka
[ocamllex](https://github.com/atom-ocaml/tree-sitter-ocamllex) | community | `H  J ` | ✓ | @undu
[odin](https://github.com/tree-sitter-grammars/tree-sitter-odin) | core | `HFIJL` | | @amaanq
[org](https://github.com/milisims/tree-sitter-org) | unsupported | `     ` | |
[pascal](https://github.com/Isopod/tree-sitter-pascal.git) | community | `HFIJL` | | @Isopod
[pascal](https://github.com/Isopod/tree-sitter-pascal) | community | `HFIJL` | | @Isopod
[passwd](https://github.com/ath3/tree-sitter-passwd) | community | `H    ` | | @amaanq
[pem](https://github.com/tree-sitter-grammars/tree-sitter-pem) | core | `HF J ` | | @ObserverOfTime
[perl](https://github.com/tree-sitter-perl/tree-sitter-perl) | community | `HF J ` | | @RabbiVeesh, @LeoNerd
@ -245,8 +245,9 @@ jsx (queries only)[^jsx] | community | `HFIJ ` | | @steelsojka
[swift](https://github.com/alex-pinkus/tree-sitter-swift) | community | `H I L` | ✓ | @alex-pinkus
[sxhkdrc](https://github.com/RaafatTurki/tree-sitter-sxhkdrc) | community | `HF J ` | | @RaafatTurki
[systemtap](https://github.com/ok-ryoko/tree-sitter-systemtap) | community | `HF JL` | | @ok-ryoko
[t32](https://gitlab.com/xasc/tree-sitter-t32.git) | community | `HFIJL` | | @xasc
[t32](https://gitlab.com/xasc/tree-sitter-t32) | community | `HFIJL` | | @xasc
[tablegen](https://github.com/tree-sitter-grammars/tree-sitter-tablegen) | core | `HFIJL` | | @amaanq
[tact](https://github.com/tact-lang/tree-sitter-tact) | community | `HFIJL` | | @novusnota
[tcl](https://github.com/tree-sitter-grammars/tree-sitter-tcl) | core | `HFI  ` | | @lewis6991
[teal](https://github.com/euclidianAce/tree-sitter-teal) | community | `HFIJL` | ✓ | @euclidianAce
[templ](https://github.com/vrischmann/tree-sitter-templ) | community | `H  J ` | | @vrischmann
@ -256,13 +257,14 @@ jsx (queries only)[^jsx] | community | `HFIJ ` | | @steelsojka
[tiger](https://github.com/ambroisie/tree-sitter-tiger) | community | `HFIJL` | | @ambroisie
[tlaplus](https://github.com/tlaplus-community/tree-sitter-tlaplus) | community | `HF JL` | | @ahelwer, @susliko
[tmux](https://github.com/Freed-Wu/tree-sitter-tmux) | community | `H  J ` | | @Freed-Wu
[todotxt](https://github.com/arnarg/tree-sitter-todotxt.git) | community | `H    ` | | @arnarg
[todotxt](https://github.com/arnarg/tree-sitter-todotxt) | community | `H    ` | | @arnarg
[toml](https://github.com/tree-sitter-grammars/tree-sitter-toml) | core | `HFIJL` | | @tk-shirasaka
[tsv](https://github.com/tree-sitter-grammars/tree-sitter-csv) | core | `H    ` | | @amaanq
[tsx](https://github.com/tree-sitter/tree-sitter-typescript) | core | `HFIJL` | | @steelsojka
[turtle](https://github.com/BonaBeavis/tree-sitter-turtle) | community | `HFIJL` | | @BonaBeavis
[twig](https://github.com/gbprod/tree-sitter-twig) | community | `H  J ` | | @gbprod
[typescript](https://github.com/tree-sitter/tree-sitter-typescript) | core | `HFIJL` | | @steelsojka
[typespec](https://github.com/happenslol/tree-sitter-typespec) | community | `H IJ ` | | @happenslol
[typoscript](https://github.com/Teddytrombone/tree-sitter-typoscript) | community | `HFIJ ` | | @Teddytrombone
[typst](https://github.com/uben0/tree-sitter-typst) | community | `HFIJ ` | | @uben0, @RaafatTurki
[udev](https://github.com/tree-sitter-grammars/tree-sitter-udev) | core | `H  JL` | | @ObserverOfTime

View file

@ -4,13 +4,16 @@ This document lists the planned and finished changes in this rewrite towards [Nv
## TODO
- [ ] **`locals.lua`:** move to `nvim-treesitter-refactor`?
- [ ] **`parsers.lua`:** include revision? (<https://stackoverflow.com/questions/64260981>)
- [ ] **update-lockfile:** allow specifying version in addition to commit hash (for Tier 1)
- [ ] **`parsers.lua`:** include revision (serialization at home: `vim.inspect`)
- rename `parsers` to `languages`
- rename `install_info` to `parser`
- [ ] **`parsers.lua`:** allow specifying version in addition to commit hash (for Tier 1)
- [ ] **update-lockfile:** one commit per parser/tier?
- [ ] **tests:** fix, update, extend (cover all Tier 1 languages)
- [ ] **documentation:** consolidate, autogenerate?
- [ ] **documentation:** migration guide
- [ ] **`locals.lua`:** move to `nvim-treesitter-refactor`?
- [ ] **textobjects:** include simple(!) function, queries? (check Helix)
- [ ] **downstream:** adapt to breaking changes (`nvim-treesitter-refactor`)

View file

@ -37,7 +37,7 @@ To get a list of supported languages
To install supported parsers and queries, put this in your `init.lua` file:
>lua
require'nvim-treesitter.configs'.setup {
require'nvim-treesitter.config'.setup {
-- A directory to install the parsers and queries to.
-- Defaults to the `stdpath('data')/site` dir.
install_dir = "/some/path/to/store/parsers",

View file

@ -0,0 +1,47 @@
---@meta
error('Cannot require a meta file')
---@class InstallInfo
---
---URL of parser repo (Github/Gitlab)
---@field url string
---
---Commit hash of parser to download (compatible with queries)
---@field revision string
---
---Files to include when compiling (`src/parser.c` and optionally `src/scanner.c')
---@field files string[]
---
---Branch of parser repo to download (if not default branch)
---@field branch? string
---
---Location of `grammar.js` in repo (if not at root, e.g., in a monorepo)
---@field location? string
---
---Repo does not contain a `parser.c`; must be generated from grammar first
---@field generate? boolean
---
---Parser needs to be generated from `grammar.json` (generating from `grammar.js` requires npm)
---@field generate_from_json? boolean
---
---Parser repo is a local directory; overrides `url`, `revision`, and `branch`
---@field path? string
---@class ParserInfo
---
---Information necessary to build and install the parser (empty for query-only language)
---@field install_info? InstallInfo
---
---List of Github users maintaining the queries for Neovim
---@field maintainers? string[]
---
---List of other languages to install (e.g., if queries inherit from them)
---@field requires? string[]
---
---Language support tier, maps to "core", "stable", "community", "unmaintained"
---@field tier integer
---
---Explanatory footnote text to add in SUPPORTED_LANGUAGES.md
---@field readme_note? string
---@alias nvim-ts.parsers table<string,ParserInfo>

View file

@ -1,5 +1,7 @@
local M = {}
M.tiers = { 'stable', 'core', 'community', 'unsupported' }
---@class TSConfig
---@field auto_install boolean
---@field ensure_install string[]
@ -33,7 +35,7 @@ function M.setup(user_data)
local ft = vim.bo[buf].filetype
local lang = vim.treesitter.language.get_lang(ft) or ft
if
require('nvim-treesitter.parsers').configs[lang]
require('nvim-treesitter.parsers')[lang]
and not vim.list_contains(M.installed_parsers(), lang)
and not vim.list_contains(config.ignore_install, lang)
then
@ -51,7 +53,7 @@ function M.setup(user_data)
local to_install = M.norm_languages(config.ensure_install, { ignored = true, installed = true })
if #to_install > 0 then
require('nvim-treesitter.install').install(to_install)
require('nvim-treesitter.install').install(to_install, { force = true })
end
end
end
@ -85,6 +87,52 @@ function M.installed_parsers()
return installed
end
-- Get a list of all available parsers
---@param tier integer? only get parsers of specified tier
---@return string[]
function M.get_available(tier)
local parsers = require('nvim-treesitter.parsers')
--- @type string[]
local languages = vim.tbl_keys(parsers)
table.sort(languages)
if tier then
languages = vim.tbl_filter(
--- @param p string
function(p)
return parsers[p].tier == tier
end,
languages
)
end
if vim.fn.executable('tree-sitter') == 0 then
languages = vim.tbl_filter(
--- @param p string
function(p)
return parsers[p].install_info ~= nil and not parsers[p].install_info.generate
end,
languages
)
end
return languages
end
local function expand_tiers(list)
for i, tier in ipairs(M.tiers) do
if vim.list_contains(list, tier) then
list = vim.tbl_filter(
--- @param l string
function(l)
return l ~= tier
end,
list
)
vim.list_extend(list, M.get_available(i))
end
end
return list
end
---Normalize languages
---@param languages? string[]|string
---@param skip? { ignored: boolean, missing: boolean, installed: boolean, dependencies: boolean }
@ -96,32 +144,11 @@ function M.norm_languages(languages, skip)
languages = { languages }
end
local parsers = require('nvim-treesitter.parsers')
if vim.list_contains(languages, 'all') then
if skip and skip.missing then
return M.installed_parsers()
end
languages = parsers.get_available()
end
-- keep local to avoid leaking parsers module
--- @param list string[]
local function expand_tiers(list)
for i, tier in ipairs(parsers.tiers) do
if vim.list_contains(list, tier) then
list = vim.tbl_filter(
--- @param l string
function(l)
return l ~= tier
end,
list
)
vim.list_extend(list, parsers.get_available(i))
end
end
return list
languages = M.get_available()
end
languages = expand_tiers(languages)
@ -159,19 +186,20 @@ function M.norm_languages(languages, skip)
)
end
local parsers = require('nvim-treesitter.parsers')
languages = vim.tbl_filter(
--- @param v string
function(v)
-- TODO(lewis6991): warn of any unknown parsers?
return parsers.configs[v] ~= nil
return parsers[v] ~= nil
end,
languages
)
if not (skip and skip.dependencies) then
for _, lang in pairs(languages) do
if parsers.configs[lang].requires then
vim.list_extend(languages, parsers.configs[lang].requires)
if parsers[lang].requires then
vim.list_extend(languages, parsers[lang].requires)
end
end
end

View file

@ -134,7 +134,7 @@ function M.check()
health.start('Installed languages' .. string.rep(' ', 5) .. 'H L F I J')
local languages = config.installed_parsers()
for _, lang in pairs(languages) do
local parser = parsers.configs[lang]
local parser = parsers[lang]
local out = lang .. string.rep(' ', 22 - #lang)
if parser.install_info then
for _, query_group in pairs(M.bundled_queries) do

View file

@ -25,12 +25,6 @@ local uv_unlink = a.wrap(uv.fs_unlink, 2)
local M = {}
---@class LockfileInfo
---@field revision string
---@type table<string, LockfileInfo>
local lockfile = {}
local max_jobs = 10
local iswin = uv.os_uname().sysname == 'Windows_NT'
@ -63,7 +57,7 @@ end
---@param lang string
---@return InstallInfo?
local function get_parser_install_info(lang)
local parser_config = parsers.configs[lang]
local parser_config = parsers[lang]
if not parser_config then
log.error('Parser not available for language "' .. lang .. '"')
@ -78,24 +72,6 @@ function M.get_package_path(...)
return fs.joinpath(fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':p:h:h:h'), ...)
end
---@param lang string
---@return string?
local function get_target_revision(lang)
local info = get_parser_install_info(lang)
if info and info.revision then
return info.revision
end
if #lockfile == 0 then
local filename = M.get_package_path('lockfile.json')
lockfile = vim.json.decode(util.read_file(filename)) --[[@as table<string, LockfileInfo>]]
end
if lockfile[lang] then
return lockfile[lang].revision
end
end
---@param lang string
---@return string?
local function get_installed_revision(lang)
@ -106,9 +82,9 @@ end
---@param lang string
---@return boolean
local function needs_update(lang)
local revision = get_target_revision(lang)
if revision then
return revision ~= get_installed_revision(lang)
local info = get_parser_install_info(lang)
if info and info.revision then
return info.revision ~= get_installed_revision(lang)
end
-- No revision. Check the queries link to the same place
@ -415,7 +391,7 @@ local function install_lang0(lang, cache_dir, install_dir, generate)
local project_name = 'tree-sitter-' .. lang
local revision = get_target_revision(lang)
local revision = repo.revision
local compile_location ---@type string
if repo.path then
@ -525,40 +501,33 @@ local function install_lang(lang, cache_dir, install_dir, force, generate)
return status
end
--- Reload the parser table and user modifications in case of update
local function reload_parsers()
package.loaded['nvim-treesitter.parsers'] = nil
parsers = require('nvim-treesitter.parsers')
vim.api.nvim_exec_autocmds('User', { pattern = 'TSUpdate' })
end
---@class InstallOptions
---@field force? boolean
---@field generate? boolean
---@field skip? table
--- Install a parser
--- @param languages? string[]|string
--- @param languages string[]
--- @param options? InstallOptions
--- @param _callback? fun()
local function install(languages, options, _callback)
options = options or {}
local force = options.force
local generate = options.generate
local skip = options.skip
local cache_dir = vim.fs.normalize(fn.stdpath('cache'))
local install_dir = config.get_install_dir('parser')
if not languages or type(languages) == 'string' then
languages = { languages }
end
if languages[1] == 'all' then
force = true
end
languages = config.norm_languages(languages, skip)
local tasks = {} --- @type fun()[]
local done = 0
for _, lang in ipairs(languages) do
tasks[#tasks + 1] = a.sync(function()
a.main()
local status = install_lang(lang, cache_dir, install_dir, force, generate)
local status = install_lang(lang, cache_dir, install_dir, options.force, options.generate)
if status ~= 'failed' then
done = done + 1
end
@ -572,7 +541,20 @@ local function install(languages, options, _callback)
end
end
M.install = a.sync(install, 2)
M.install = a.sync(function(languages, options, _callback)
reload_parsers()
if not languages or #languages == 0 then
languages = 'all'
end
languages = config.norm_languages(languages, options and options.skip)
if languages[1] == 'all' then
options.force = true
end
install(languages, options)
end, 2)
---@class UpdateOptions
@ -580,8 +562,7 @@ M.install = a.sync(install, 2)
---@param _options? UpdateOptions
---@param _callback function
M.update = a.sync(function(languages, _options, _callback)
M.lockfile = {}
reload_parsers()
if not languages or #languages == 0 then
languages = 'all'
end

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ local function complete_available_parsers(arglead)
function(v)
return v:find(arglead) ~= nil
end,
require('nvim-treesitter.parsers').get_available()
require('nvim-treesitter.config').get_available()
)
end

View file

@ -2,7 +2,7 @@
vim.opt.runtimepath:append('.')
local query_types = require('nvim-treesitter.health').bundled_queries
local configs = require('nvim-treesitter.parsers').configs
local configs = require('nvim-treesitter.parsers')
local parsers = #_G.arg > 0 and { unpack(_G.arg) }
or require('nvim-treesitter.config').installed_parsers()

21
scripts/convert-lockfile.lua Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env -S nvim -l
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers')
local filename = require('nvim-treesitter.install').get_package_path('lockfile.json')
local lockfile = vim.json.decode(util.read_file(filename)) --[[@as table<string,{revision:string}>]]
for k, p in pairs(parsers) do
if p.install_info then
p.install_info.revision = lockfile[k].revision
end
end
-- write new parser file
local header = '---@type nvim-ts.parsers\nreturn '
local parser_file = header .. vim.inspect(parsers)
if vim.fn.executable('stylua') == 1 then
parser_file = vim.system({ 'stylua', '-' }, { stdin = parser_file }):wait().stdout --[[@as string]]
end
util.write_file('lua/nvim-treesitter/parsers.lua', parser_file)

View file

@ -1,64 +0,0 @@
#!/usr/bin/env -S nvim -l
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers').configs
-- Load previous lockfile
local filename = require('nvim-treesitter.install').get_package_path('lockfile.json')
local old_lockfile = vim.json.decode(util.read_file(filename)) --[[@as table<string,{revision:string}>]]
local jobs = {} ---@type table<string,vim.SystemObj>
local new_lockfile = {} ---@type table<string,{revision:string}>
local updates = {} ---@type string[]
-- check for new revisions
for k, p in pairs(parsers) do
if p.tier == 4 then
new_lockfile[k] = old_lockfile[k]
print('Skipping ' .. k)
elseif p.install_info then
print('Updating ' .. k)
jobs[k] = vim.system({ 'git', 'ls-remote', p.install_info.url })
end
if #vim.tbl_keys(jobs) % 100 == 0 or next(parsers, k) == nil then
for name, job in pairs(jobs) do
local stdout = vim.split(job:wait().stdout, '\n')
jobs[name] = nil
local branch = parsers[name].install_info.branch
local line = 1
if branch then
for j, l in ipairs(stdout) do
if l:find(vim.pesc(branch)) then
line = j
break
end
end
end
local sha = vim.split(stdout[line], '\t')[1]
new_lockfile[name] = { revision = sha }
if new_lockfile[name].revision ~= old_lockfile[name].revision then
updates[#updates + 1] = name
end
end
end
end
assert(#vim.tbl_keys(jobs) == 0)
if #updates > 0 then
-- write new lockfile
local lockfile_json = vim.json.encode(new_lockfile) --[[@as string]]
if vim.fn.executable('jq') == 1 then
lockfile_json =
assert(vim.system({ 'jq', '--sort-keys' }, { stdin = lockfile_json }):wait().stdout)
end
util.write_file(filename, lockfile_json)
print(string.format('\nUpdated parsers: %s', table.concat(updates, ', ')))
else
print('\nAll parsers up to date!')
end

65
scripts/update-parsers.lua Executable file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env -S nvim -l
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers')
local jobs = {} ---@type table<string,vim.SystemObj>
local updates = {} ---@type string[]
-- check for new revisions
for k, p in pairs(parsers) do
if p.tier < 5 and p.install_info then
print('Updating ' .. k)
jobs[k] = vim.system({ 'git', 'ls-remote', p.install_info.url })
end
if #vim.tbl_keys(jobs) % 100 == 0 or next(parsers, k) == nil then
for name, job in pairs(jobs) do
local stdout = vim.split(job:wait().stdout, '\n')
jobs[name] = nil
local info = parsers[name].install_info
assert(info)
local branch = info.branch
local line = 1
if branch then
for j, l in ipairs(stdout) do
if l:find(vim.pesc(branch)) then
line = j
break
end
end
end
local sha = vim.split(stdout[line], '\t')[1]
if info.revision ~= sha then
info.revision = sha
updates[#updates + 1] = name
end
end
end
end
assert(#vim.tbl_keys(jobs) == 0)
if #updates > 0 then
-- write new parser file
local header = '---@type nvim-ts.parsers\nreturn '
local parser_file = header .. vim.inspect(parsers)
if vim.fn.executable('stylua') == 1 then
parser_file = vim.system({ 'stylua', '-' }, { stdin = parser_file }):wait().stdout --[[@as string]]
end
util.write_file('lua/nvim-treesitter/parsers.lua', parser_file)
local update_list = table.concat(updates, ', ')
print(string.format('\nUpdated parsers: %s', update_list))
-- pass list to workflow
if os.getenv('GITHUB_ENV') then
local env = io.open(os.getenv('GITHUB_ENV'), 'a')
env:write(string.format('UPDATED_PARSERS=%s\n', update_list))
env:close()
end
else
print('\nAll parsers up to date!')
end

View file

@ -2,12 +2,13 @@
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers')
local tiers = require('nvim-treesitter.config').tiers
---@class Parser
---@field name string
---@field parser ParserInfo
local sorted_parsers = {}
for k, v in pairs(parsers.configs) do
for k, v in pairs(parsers) do
table.insert(sorted_parsers, { name = k, parser = v })
end
table.sort(sorted_parsers, function(a, b)
@ -45,7 +46,7 @@ for _, v in ipairs(sorted_parsers) do
end
-- tier
generated_text = generated_text .. (p.tier and parsers.tiers[p.tier] or '') .. ' | '
generated_text = generated_text .. (p.tier and tiers[p.tier] or '') .. ' | '
-- queries
generated_text = generated_text

View file

@ -64,7 +64,7 @@
mod1 = {
type = "lua";
config = ''
require('nvim-treesitter.configs').setup()
require('nvim-treesitter.config').setup()
'';
};
}