├── .dir-locals.el ├── .github ├── dependabot.yml └── workflows │ ├── doc.yml │ ├── integ.yml │ ├── main.yml │ ├── release.yml │ ├── shell.yml │ └── update-submodules.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Eask ├── LICENSE ├── Makefile ├── README.md ├── bin ├── build ├── build.ps1 ├── dev ├── ensure-lang ├── ensure-lang.ps1 ├── env.bash ├── inspect-binaries ├── inspect-binaries.ps1 ├── package ├── package.ps1 ├── setup ├── setup.ps1 ├── test └── test.ps1 ├── core ├── .cargo │ └── config.toml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── Eask ├── src │ ├── cursor.rs │ ├── error.rs │ ├── lang.rs │ ├── lib.rs │ ├── node.rs │ ├── parser.rs │ ├── query.rs │ ├── tree.rs │ └── types.rs ├── tsc-dyn-get.el ├── tsc-obsolete.el └── tsc.el ├── doc ├── -config-ox-hugo.el ├── config.toml ├── emacs-tree-sitter.org ├── layouts │ ├── partials │ │ ├── XXX │ │ ├── custom-head.html │ │ ├── docdock │ │ │ ├── custom-head.html │ │ │ ├── header.html │ │ │ ├── menu.html │ │ │ ├── next-prev-page.html │ │ │ ├── tree-of-menu.html │ │ │ └── tree-of-sections.html │ │ ├── header.html │ │ ├── menu.html │ │ └── next-prev-page.html │ └── shortcodes │ │ └── notice.html └── static │ ├── css │ └── xxx.css │ ├── images │ └── favicon.png │ └── img │ ├── emacs-tree-sitter-96x96.png │ ├── emacs-tree-sitter.png │ └── emacs-tree-sitter.xcf ├── lisp ├── tree-sitter-cli.el ├── tree-sitter-debug.el ├── tree-sitter-extras.el ├── tree-sitter-hl.el ├── tree-sitter-load.el ├── tree-sitter-query.el └── tree-sitter.el └── tests ├── .gitattributes ├── data ├── change-case-region.rs ├── delete-non-ascii-text.rs ├── extend-region.rs ├── hl-region-vs-query-region.js ├── hl.py ├── narrowing.bash ├── query.rs ├── range-restriction-and-early-termination.c └── types.rs ├── tree-sitter-bench.el ├── tree-sitter-tests-utils.el ├── tree-sitter-tests.el └── tsc-dyn-get-tests.el /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((emacs-lisp-mode 5 | (indent-tabs-mode . nil) 6 | (sentence-end-double-space . nil) 7 | (fill-column . 80) 8 | (emacs-lisp-docstring-fill-column . 80) 9 | (comment-fill-column . 80))) 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Doc 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - doc 8 | paths: 9 | - doc/** 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | publish-doc: 18 | runs-on: ubuntu-24.04 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | 24 | - name: Install Hugo 25 | run: | 26 | export HUGO_VERSION=0.92.2 27 | export HUGO_DEB="hugo_${HUGO_VERSION}_Linux-64bit.deb" 28 | wget "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${HUGO_DEB}" 29 | sudo dpkg -i "${HUGO_DEB}" 30 | hugo version 31 | 32 | - name: Set up Emacs 33 | uses: jcs090218/setup-emacs@master 34 | with: 35 | version: '27.2' 36 | 37 | - name: Generate markdown files 38 | run: bin/dev generate-doc-md 39 | 40 | - name: Check out GitHub page repo 41 | uses: actions/checkout@v4 42 | with: 43 | repository: emacs-tree-sitter/emacs-tree-sitter.github.io 44 | path: emacs-tree-sitter.github.io 45 | token: ${{ secrets.PAT }} 46 | 47 | - name: Build doc artifacts with Hugo 48 | run: hugo --destination ../emacs-tree-sitter.github.io 49 | working-directory: doc 50 | 51 | # TODO: Generate better commit message 52 | - name: Publish doc artifacts 53 | run: | 54 | git config user.name github-actions 55 | git config user.email github-actions@github.com 56 | git add . 57 | git commit -F- <<-_UBLT_COMMIT_MSG_ 58 | auto: ${{ github.event.head_commit.message }} 59 | 60 | SourceCommit: https://github.com/emacs-tree-sitter/elisp-tree-sitter/commit/${{ github.sha }} 61 | _UBLT_COMMIT_MSG_ 62 | git push 63 | working-directory: emacs-tree-sitter.github.io 64 | -------------------------------------------------------------------------------- /.github/workflows/integ.yml: -------------------------------------------------------------------------------- 1 | name: Integ 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | revision: 7 | required: true 8 | default: 'master' 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | integ: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: 20 | - macos-15 21 | - ubuntu-18.04 22 | - windows-2019 23 | emacs-version: 24 | - '27.2' 25 | - '28.1' 26 | runs-on: ${{ matrix.os }} 27 | steps: 28 | - uses: jcs090218/setup-emacs@master 29 | with: 30 | version: ${{ matrix.emacs-version }} 31 | 32 | - uses: actions/checkout@v4 33 | with: 34 | ref: ${{ github.event.inputs.revision }} 35 | 36 | - uses: emacs-eask/setup-eask@master 37 | with: 38 | version: 'snapshot' 39 | 40 | - run: bin/setup 41 | 42 | - run: bin/test integ 43 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - github-actions # To test changes related to GitHub Actions. 8 | - ci # To test all CI changes. 9 | paths-ignore: 10 | - '*.md' 11 | - doc/** 12 | pull_request: 13 | branches: 14 | - master 15 | paths-ignore: 16 | - '*.md' 17 | - doc/** 18 | workflow_dispatch: 19 | 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | ci: 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | os: 30 | - macos-15 31 | - ubuntu-24.04 32 | - windows-2019 33 | emacs-version: 34 | - '28.2' 35 | - '29.4' 36 | include: 37 | # XXX: The (os, emacs-version) combination must be different from those from those above, 38 | # otherwise GitHub Actions modifies the existing combinations, instead of adding new. 39 | - os: macos-15 40 | emacs-version: '28.2' 41 | # Cross build 42 | target: aarch64-apple-darwin 43 | - os: macos-15 44 | emacs-version: '29.4' 45 | # Cross build 46 | target: aarch64-apple-darwin 47 | runs-on: ${{ matrix.os }} 48 | steps: 49 | - name: Set up Rust 50 | run: | 51 | rustup install stable 52 | rustc -Vv 53 | cargo -V 54 | - name: Set up Rust's cross-build target 55 | if: matrix.target 56 | run: | 57 | rustup target add ${{ matrix.target }} 58 | 59 | - uses: jcs090218/setup-emacs@master 60 | with: 61 | version: ${{ matrix.emacs-version }} 62 | 63 | - uses: actions/checkout@v4 64 | with: 65 | submodules: true 66 | 67 | - uses: emacs-eask/setup-eask@master 68 | with: 69 | version: 'snapshot' 70 | 71 | - run: bin/setup 72 | - run: bin/build -target "${{ matrix.target }}" 73 | 74 | - run: bin/inspect-binaries 75 | continue-on-error: true 76 | 77 | - name: Install tree-sitter CLI 78 | if: ${{ !matrix.target }} 79 | run: npm install -g tree-sitter-cli@0.19.3 80 | - run: eask install-deps --dev 81 | - run: bin/test 82 | if: ${{ !matrix.target }} 83 | - run: bin/test bench 84 | if: ${{ !matrix.target }} 85 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | - '!melpa-stable*' 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | fail-fast: true 18 | matrix: 19 | # The `os` versions below should be as low as possible, for better compatibility. 20 | include: 21 | # TODO: Get host platform from rustc instead of specifying it explicitly. 22 | - os: macos-15 23 | emacs-version: '28.2' 24 | ext: dylib 25 | host: x86_64-apple-darwin 26 | - os: macos-15 27 | emacs-version: '28.2' 28 | ext: dylib 29 | host: aarch64-apple-darwin 30 | target: aarch64-apple-darwin 31 | - os: ubuntu-24.04 32 | emacs-version: '27.2' 33 | ext: so 34 | host: x86_64-unknown-linux-gnu 35 | - os: windows-2019 36 | emacs-version: '27.2' 37 | ext: dll 38 | host: x86_64-pc-windows-msvc 39 | runs-on: ${{ matrix.os }} 40 | steps: 41 | - name: Set up Rust 42 | run: | 43 | rustup install stable 44 | rustc -Vv 45 | cargo -V 46 | - name: Set up Rust's cross-build target 47 | if: matrix.target 48 | run: | 49 | rustup target add ${{ matrix.target }} 50 | 51 | - uses: jcs090218/setup-emacs@master 52 | with: 53 | version: ${{ matrix.emacs-version }} 54 | 55 | - uses: emacs-eask/setup-eask@master 56 | with: 57 | version: 'snapshot' 58 | 59 | - uses: actions/checkout@v4 60 | with: 61 | submodules: true 62 | 63 | - run: bin/setup 64 | 65 | - run: bin/build -target "${{ matrix.target }}" 66 | continue-on-error: true 67 | 68 | - run: bin/inspect-binaries 69 | continue-on-error: true 70 | 71 | - name: Install tree-sitter CLI 72 | if: ${{ !matrix.target }} 73 | run: npm install -g tree-sitter-cli@0.19.3 74 | 75 | - run: eask install-deps --dev 76 | 77 | - run: bin/test 78 | if: ${{ !matrix.target }} 79 | continue-on-error: true 80 | 81 | - run: bin/test bench 82 | if: ${{ !matrix.target }} 83 | 84 | - name: Rename cross-build's binary 85 | if: matrix.target 86 | run: | 87 | mv core/tsc-dyn.${{ matrix.ext }} core/tsc-dyn.${{ matrix.target }}.${{ matrix.ext }} 88 | 89 | - name: Make a target-explicit copy of native-build's binary 90 | if: ${{ !matrix.target && matrix.host }} 91 | shell: bash 92 | run: | 93 | cp core/tsc-dyn.${{ matrix.ext }} core/tsc-dyn.${{ matrix.host }}.${{ matrix.ext }} 94 | 95 | - name: Upload binary 96 | uses: actions/upload-artifact@v4 97 | with: 98 | name: tsc-dyn.${{ matrix.host }} 99 | path: core/tsc-dyn.*${{ matrix.ext }} 100 | if-no-files-found: error 101 | 102 | publish-binaries: 103 | needs: build 104 | runs-on: ubuntu-24.04 105 | steps: 106 | - uses: jcs090218/setup-emacs@master 107 | with: 108 | version: '27.2' 109 | - uses: actions/checkout@v4 110 | - name: Generate release notes 111 | run: | 112 | bin/dev release-notes $(git describe --tags --abbrev=0) > RELEASE-NOTES 113 | cat RELEASE-NOTES 114 | 115 | - name: Download binaries (x86_64-apple-darwin) 116 | uses: actions/download-artifact@v4 117 | with: 118 | name: tsc-dyn.x86_64-apple-darwin 119 | 120 | - name: Download binaries (aarch64-apple-darwin) 121 | uses: actions/download-artifact@v4 122 | with: 123 | name: tsc-dyn.aarch64-apple-darwin 124 | 125 | - name: Download binaries (x86_64-unknown-linux-gnu) 126 | uses: actions/download-artifact@v4 127 | with: 128 | name: tsc-dyn.x86_64-unknown-linux-gnu 129 | 130 | - name: Download binaries (x86_64-pc-windows-msvc) 131 | uses: actions/download-artifact@v4 132 | with: 133 | name: tsc-dyn.x86_64-pc-windows-msvc 134 | 135 | - run: ls -R 136 | 137 | - name: Create GitHub Release 138 | uses: softprops/action-gh-release@v1 139 | with: 140 | draft: false 141 | body_path: RELEASE-NOTES 142 | files: | 143 | tsc-dyn.* 144 | 145 | integ: 146 | needs: publish-binaries 147 | strategy: 148 | fail-fast: false 149 | matrix: 150 | include: 151 | - os: macos-15 152 | emacs-version: '27.2' 153 | - os: ubuntu-24.04 154 | emacs-version: '27.2' 155 | # XXX: Fails on CI, but not locally (Windows 10) with 156 | # :value 2 :fail-reason "did not signal an error" 157 | # and 158 | # (different-types 443 (tsc . 1)) 159 | # 160 | # - os: windows-2019 161 | # emacs-version: '27.2' 162 | runs-on: ${{ matrix.os }} 163 | steps: 164 | - uses: jcs090218/setup-emacs@master 165 | with: 166 | version: ${{ matrix.emacs-version }} 167 | 168 | - uses: actions/checkout@v4 169 | 170 | - uses: emacs-eask/setup-eask@master 171 | with: 172 | version: 'snapshot' 173 | 174 | - run: bin/setup 175 | 176 | - run: bin/test integ 177 | 178 | publish-melpa: 179 | needs: integ 180 | runs-on: ubuntu-24.04 181 | steps: 182 | - uses: actions/checkout@v4 183 | 184 | - name: Set release version 185 | run: | 186 | echo "RELEASE_VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV 187 | 188 | - name: Greenlight releasing to MELPA 189 | uses: actions/github-script@v7 190 | with: 191 | github-token: ${{ secrets.PAT }} 192 | script: | 193 | github.rest.git.updateRef({ 194 | owner: context.repo.owner, 195 | repo: context.repo.repo, 196 | ref: "heads/release", 197 | sha: context.sha 198 | }) 199 | continue-on-error: true 200 | 201 | # We don't want this to trigger workflows, so we don't use a personal access token. 202 | - name: Greenlight releasing to MELPA Stable 203 | uses: actions/github-script@v7 204 | with: 205 | script: | 206 | github.rest.git.createRef({ 207 | owner: context.repo.owner, 208 | repo: context.repo.repo, 209 | ref: "refs/tags/melpa-stable/v${{ env.RELEASE_VERSION }}", 210 | sha: context.sha 211 | }) 212 | continue-on-error: true 213 | -------------------------------------------------------------------------------- /.github/workflows/shell.yml: -------------------------------------------------------------------------------- 1 | name: Shell 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | revision: 7 | required: true 8 | default: 'master' 9 | platform: 10 | required: true 11 | default: macos-latest 12 | description: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | build: 20 | runs-on: ${{ github.event.inputs.platform }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | ref: ${{ github.event.inputs.revision }} 25 | - uses: mxschmitt/action-tmate@v3 26 | name: Setup tmate session 27 | -------------------------------------------------------------------------------- /.github/workflows/update-submodules.yml: -------------------------------------------------------------------------------- 1 | name: Update Submodules 2 | 3 | on: 4 | schedule: 5 | - cron: '0 * * * *' 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | persist-credentials: false 19 | fetch-depth: 0 20 | 21 | - name: Submodule update 22 | run: | 23 | git submodule init 24 | git submodule update --remote --merge 25 | 26 | - name: Create Pull Request 27 | uses: peter-evans/create-pull-request@v6 28 | with: 29 | title: 'Update submodules' 30 | body: '' 31 | commit-message: 'Update all submodules' 32 | branch: submodules-update 33 | delete-branch: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.elc* 2 | **/.cask 3 | **/.eask 4 | **/dist 5 | 6 | # OS generated 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "langs"] 2 | path = langs 3 | url = https://github.com/emacs-tree-sitter/tree-sitter-langs 4 | ignore = all 5 | 6 | [submodule "doc/themes/docdock"] 7 | path = doc/themes/docdock 8 | url = https://github.com/emacs-tree-sitter/hugo-theme-docdock 9 | ignore = all 10 | 11 | [submodule "doc/ox-hugo"] 12 | path = doc/ox-hugo 13 | url = https://github.com/emacs-tree-sitter/ox-hugo 14 | ignore = all 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 5 | 6 | ## [Unreleased] 7 | - Make button without newline (#259) 8 | 9 | ## [0.18.0] - 2022-02-12 10 | - Added APIs to traverse the syntax tree: `tsc-traverse-do`, `tsc-traverse-mapc`, `tsc-traverse-iter`. The traversal is depth-first pre-order. 11 | - Improved syntax tree rendering's performance in `tree-sitter-debug`. 12 | - Added optional params `props` and `output` to `tsc-current-node`, which allows retrieving node properties, instead of the node object itself. This enables performance optimizations in uses cases that deal with a large number of nodes. 13 | 14 | ## [0.17.0] - 2022-01-29 15 | - Added customization option `tsc-dyn-get-from`, which is a list of sources to get the dynamic module `tsc-dyn` from. Its default value is `(:github :compilation)`. 16 | - Made `tree-sitter-hl`'s region-fontification function fall back to the underlying non-tree-sitter function when called outside of `tree-sitter-hl-mode`. This fixes an issue where `jupyter-repl-mode`'s [input cells are not highlighted](https://github.com/nnicandro/emacs-jupyter/issues/363). 17 | - Updated `tsc-dyn-get` to download platform-specific binaries (mainly for Apple Silicon). 18 | 19 | ## [0.16.1] - 2021-12-11 20 | - Modified CI pipelines to publish additional pre-built dynamic modules. Their filenames include the platform they are built for. The files without platform in name will eventually be deprecated. 21 | + `tsc-dyn.x86_64-apple-darwin.dylib` (same as `tsc-dyn.dylib`) 22 | + `tsc-dyn.x86_64-unknown-linux-gnu.so` (same as `tsc-dyn.so`) 23 | + `tsc-dyn.x86_64-pc-windows-msvc.dll` (same as `tsc-dyn.dll`) 24 | + `tsc-dyn.aarch64-apple-darwin.dylib` (new, for Apple Silicon) 25 | 26 | ## [0.16.0] - 2021-12-08 27 | - Upgraded `tree-sitter` crate to 0.20.0, which: 28 | + Changed the semantics of range-restricted query to report matches that intersect the range, instead of only fully-contained matches. See [tree-sitter#1130](https://github.com/tree-sitter/tree-sitter/pull/1130). 29 | + Fixed [an issue](https://github.com/tree-sitter/tree-sitter/pull/1372#issuecomment-924958513) where multiple patterns with the same capture names can result in the first capture being omitted. 30 | - Improved performance: 31 | + Disabled query-region extension. Added a flag to turn it back on: `tree-sitter-hl-enable-query-region-extension`. 32 | + Increased default chunk size for parsing from 1024 to 4096. 33 | 34 | ## [0.15.2] - 2021-09-12 35 | - Reduced GC pressure by not making the text property `face` a list if there is only one face. 36 | - Recast `tree-sitter-node-at-point` as more general `tree-sitter-node-at-pos`, taking optional POS argument. 37 | - Made `tree-sitter-node-at-pos` accept special node-type arguments `:named` and `:anonymous`. 38 | 39 | ## [0.15.1] - 2021-03-20 40 | - Fixed some invalid query patterns [causing SIGABRT](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/125), by upgrading `tree-sitter` crate. 41 | - Used keywords to represent auxiliary (invisible) node types. For example: `:end`, `:_expression`. 42 | 43 | ## [0.15.0] - 2021-03-15 44 | - Upgraded `tree-sitter` crate to 0.19.3, which: 45 | + Added [negated-field query patterns](https://github.com/tree-sitter/tree-sitter/pull/983). 46 | + Fixed some bugs, mostly query-related. 47 | + Is required to support newer versions of the language grammars. 48 | + Raised the minimum and maximum supported language ABI versions to 13. Older versions of the language bundle `tree-sitter-langs` (before 0.10.0) will not be loaded. 49 | 50 | ## [0.14.0] - 2021-03-10 51 | - Added ABI compatibility checks when loading a language object from a dynamic library. 52 | - Made `tsc-make-query` signal concrete error symbols, instead of `rust-panic`. 53 | 54 | ## [0.13.1] - 2021-01-16 55 | - Used static linking for C runtime on Windows, to avoid having to install VC++ redistributable package. 56 | 57 | ## [0.13.0] - 2020-12-29 58 | - Upgraded `emacs` crate to [0.15.0](https://github.com/ubolonton/emacs-module-rs/releases/tag/0.15.0) to improve performance on Emacs 27+. 59 | - Fixed the [highlighting error when exporting org as html](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/74), by removing the hack that allows `tree-sitter-hl` to work without (a major mode) setting up `font-lock-defaults`. 60 | 61 | ## [0.12.2] - 2020-12-15 62 | - Added warning after upgrading `tsc` if it requires a new version of the dynamic module `tsc-dyn`, but an older version was already loaded. 63 | - Improved language loading mechanism's tolerance of hyphens in language names. 64 | 65 | ## [0.12.1] - 2020-11-04 66 | - Fixed incorrect parsing when after-change's start position is not the same as before-change's start position. For example, this happens when calling `upcase-region` on a region whose first character is already upcased. 67 | - Upgraded `emacs` crate to [0.14.1](https://github.com/ubolonton/emacs-module-rs/releases/tag/0.14.1) to fix the [compilation error on Rust 1.47](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/62). 68 | - Upgraded `tree-sitter` crate to 0.17.1 to [fix](https://github.com/tree-sitter/tree-sitter/issues/790) [handling of repeated field names in queries](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/67). 69 | 70 | ## [0.12.0] - 2020-10-13 71 | - Moved the core APIs from `tree-sitter-core.el` into their own package `tsc`, to prepare for [distribution through MELPA](https://github.com/melpa/melpa/pull/7112). Also changed their prefix from `ts-` to `tsc-`, to avoid [conflict with `ts.el`](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/35). 72 | 73 | ## [0.11.1] - 2020-10-03 74 | - Made `tree-sitter-hl-mode` a "no-op" when `tree-sitter-hl-default-patterns` is nil. 75 | 76 | ## [0.11.0] - 2020-09-26 77 | - Upgraded `tree-sitter` crate to [fix](https://github.com/tree-sitter/tree-sitter/pull/644) an issue where [query captures miss some nodes](https://github.com/tree-sitter/tree-sitter/issues/659). This also added a check for definitely-invalid patterns when creating a query. 78 | 79 | ## [0.10.0] - 2020-08-01 80 | - Used keywords instead of strings for field names. 81 | + Replaced `ts-field-name-for-id`, `ts-field-id-for-name` with `ts-lang-field`, `ts-lang-field-id`. 82 | + Replaced `ts-current-field-name` with `ts-current-field`. 83 | + Replaced `ts-get-child-by-field-name` with `ts-get-child-by-field`. 84 | - Used symbols for named node types. 85 | + Replaced `ts-type-name-for-id` with `ts-lang-node-type`. 86 | + Added `ts-lang-node-type-id`. 87 | + Changed the return type of `ts-node-type`. 88 | - Renamed `ts-type-named-p` to `ts-lang-node-type-named-p`. 89 | - Added optional param `NODE-TYPE` to `tree-sitter-node-at-point`. 90 | - Upgraded `tree-sitter` crate to get support for `.not-match?` predicate. 91 | 92 | ## [0.9.2] - 2020-07-20 93 | - Upgraded `tree-sitter` crate to add `.` as a valid start of predicates, in addition to `#`. 94 | 95 | ## [0.9.1] - 2020-07-19 96 | - Upgraded `tree-sitter` crate to fix [incorrect capture handling](https://github.com/tree-sitter/tree-sitter/issues/685) when querying with range restriction. 97 | 98 | ## [0.9.0] - 2020-07-18 99 | - Changed `tree-sitter-hl-add-patterns` to support language-specific patterns, in addition to buffer-local patterns. 100 | 101 | ## [0.8.3] - 2020-07-12 102 | - Fixed incorrect highlighting when region-to-highlight's boundaries cut query patterns in halves. 103 | 104 | ## [0.8.2] - 2020-06-30 105 | - Upgraded `tree-sitter` crate to [fix](https://github.com/tree-sitter/tree-sitter/pull/661) handling of alternations under field names. 106 | 107 | ## [0.8.1] - 2020-06-28 108 | - Added customization option `tree-sitter-hl-use-font-lock-keywords`, allowing `tree-sitter-hl-mode` to work with minor modes that use `font-lock-add-keywords`. 109 | 110 | ## [0.8.0] - 2020-06-07 111 | - Upgraded `tree-sitter` to 0.16.1. This significantly improved the expressiveness and power of tree queries. 112 | - Made `tree-sitter-hl-mode` work without a major mode. 113 | - Add more highlighting faces to `tree-sitter-hl`. 114 | - Made `tree-sitter-core` automatically download `tree-sitter-dyn` binary when first compiled/loaded. 115 | - Added [documentation](https://emacs-tree-sitter.github.io/). 116 | 117 | ## [0.7.0] - 2020-05-02 118 | - Added `global-tree-sitter-mode`. 119 | - Added library `tree-sitter-hl`, which provides query-based syntax highlighting by overriding certain parts of `font-lock`. 120 | - Reworked query APIs for performance and clarity, most notably `ts-make-query`, `ts-query-matches`, `ts-query-captures`. 121 | - Fixed incorrect parsing caused by misunderstandings of Emacs's clumsy change tracking machinery. 122 | 123 | ## [0.6.0] - 2020-04-11 124 | - Renamed `ts-parse` into `ts-parse-chunks`, to avoid [conflict with `ts.el`](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/35). 125 | - Added library `tree-sitter-query`, which enables interactively building queries. 126 | 127 | ## [0.5.0] - 2020-03-17 128 | - Added functions `ts-node-position-range`, `ts-node-eq`. 129 | - Added function `tree-sitter-node-at-point`. 130 | - Added macro `tree-sitter-save-excursion`, which is useful for code formatting operations. 131 | - Added library `tree-sitter-extras`, for extra functionalities built on top of `tree-sitter-mode`. 132 | - Upgraded `tree-sitter` to 0.6.3. This fixed `ts-type-name-for-id` and `ts-field-name-for-id` crashing on out-of-bounds IDs. 133 | - Fixed `ts-reset-cursor` always signaling "already mutable borrowed" error. 134 | 135 | ## [0.4.0] - 2020-03-01 136 | 137 | - Replaced functions `ts-require-language` and `ts-load-language` with `tree-sitter-require` and `tree-sitter-load`. 138 | - Published pre-compiled `tree-sitter` through a custom ELPA. 139 | - Published the grammar bundle `tree-sitter-langs` as a separate package. 140 | 141 | ## [0.3.0] - 2020-02-21 142 | - Used Emacs's 1-based byte positions and line numbers instead of 0-based byte offsets and row coordinates. 143 | - Used cons cells instead of 2-element vectors to represent tree-sitter points and query matches/captures. 144 | 145 | ## [0.2.0] - 2020-02-02 146 | - Upgraded `tree-sitter` to 0.6.0. 147 | - Added library `tree-sitter-cli`. 148 | - Added library `tree-sitter-langs` (utilities to download pre-compiled modules and grammars). 149 | 150 | ## [0.1.0] - 2020-01-27 151 | Initial release 152 | 153 | [Unreleased]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.18.0...HEAD 154 | [0.18.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.17.0...0.18.0 155 | [0.17.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.16.1...0.17.0 156 | [0.16.1]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.16.0...0.16.1 157 | [0.16.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.15.2...0.16.0 158 | [0.15.2]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.15.1...0.15.2 159 | [0.15.1]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.15.0...0.15.1 160 | [0.15.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.14.0...0.15.0 161 | [0.14.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.13.1...0.14.0 162 | [0.13.1]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.13.0...0.13.1 163 | [0.13.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.12.2...0.13.0 164 | [0.12.2]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.12.1...0.12.2 165 | [0.12.1]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.12.0...0.12.1 166 | [0.12.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.11.1...0.12.0 167 | [0.11.1]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.11.0...0.11.1 168 | [0.11.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.10.0...0.11.0 169 | [0.10.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.9.2...0.10.0 170 | [0.9.2]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.9.1...0.9.2 171 | [0.9.1]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.9.0...0.9.1 172 | [0.9.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.8.3...0.9.0 173 | [0.8.3]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.8.2...0.8.3 174 | [0.8.2]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.8.1...0.8.2 175 | [0.8.1]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.8.0...0.8.1 176 | [0.8.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.7.0...0.8.0 177 | [0.7.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.6.0...0.7.0 178 | [0.6.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.5.0...0.6.0 179 | [0.5.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.4.0...0.5.0 180 | [0.4.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.3.0...0.4.0 181 | [0.3.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.2.0...0.3.0 182 | [0.2.0]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/0.1.0...0.2.0 183 | -------------------------------------------------------------------------------- /Eask: -------------------------------------------------------------------------------- 1 | ;; -*- mode: eask; lexical-binding: t -*- 2 | 3 | (package "tree-sitter" 4 | "0.18.0" 5 | "Incremental parsing system") 6 | 7 | (website-url "https://github.com/emacs-tree-sitter/elisp-tree-sitter") 8 | (keywords "languages" "tools" "parsers" "tree-sitter") 9 | 10 | (package-file "lisp/tree-sitter.el") 11 | (files 12 | "lisp/*.el") 13 | 14 | (script "test" "echo \"Error: no test specified\" && exit 1") 15 | 16 | (source 'melpa) 17 | 18 | (depends-on "emacs" "27.1") 19 | (depends-on "tsc") 20 | 21 | (development 22 | (depends-on "rust-mode") 23 | (depends-on "async") 24 | ) 25 | 26 | (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 27 | 28 | ;; Use GNU tar in Windows 29 | (when (memq system-type '(cygwin windows-nt ms-dos)) 30 | (setq package-build-tar-executable "C:/Program Files/Git/usr/bin/tar.exe")) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 emacs-tree-sitter maintainers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help 2 | help: ## Prints target and a help message 3 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ 4 | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 5 | 6 | .PHONY: build 7 | build: ## Build the bindings 8 | @./bin/build 9 | @echo "[!!] in Emacs add $(PWD) to load-path:\n\n\t(add-to-list 'load-path \"$(PWD)/lisp\")\n\n\t(add-to-list 'load-path \"$(PWD)/langs\")" 10 | 11 | .PHONY: ensure/% 12 | ensure/%: ## Download grammar for a given language 13 | @./bin/ensure-lang $* 14 | @echo "[!!] load it in Emacs: \n\n\t(require 'tree-sitter)\n\t(tree-sitter-require '$*)\n" 15 | 16 | .PHONY: test 17 | test: ## Run tests 18 | @./bin/test 19 | 20 | .PHONY: watch 21 | watch: ## Continuous testing (requires cargo-watch) 22 | @./bin/test watch 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ELisp Tree-sitter 2 | [![Documentation](https://img.shields.io/badge/documentation-latest-blue)](https://emacs-tree-sitter.github.io/) 3 | [![GitHub Actions](https://github.com/emacs-tree-sitter/elisp-tree-sitter/actions/workflows/main.yml/badge.svg)](https://github.com/emacs-tree-sitter/elisp-tree-sitter/actions/workflows/main.yml) 4 | 5 | For Emacs 29+, please use [the built-in integration](https://lists.gnu.org/archive/html/emacs-devel/2022-11/msg01443.html) instead of this package. 6 | 7 | This is an Emacs Lisp binding for [tree-sitter](https://tree-sitter.github.io/tree-sitter/), an incremental parsing library. It requires Emacs 25.1 or above, built with dynamic module support. 8 | 9 | It aims to be the foundation for a new breed of Emacs packages that understand code structurally. For example: 10 | - Faster, fine-grained code highlighting. 11 | - More flexible code folding. 12 | - Structural editing (like Paredit, or even better) for non-Lisp code. 13 | - More informative indexing for imenu. 14 | 15 | The author of tree-sitter articulated its merits a lot better in this [Strange Loop talk](https://www.thestrangeloop.com/2018/tree-sitter---a-new-parsing-system-for-programming-tools.html). 16 | 17 | ## Installation 18 | 19 | See the [installation section](https://emacs-tree-sitter.github.io/installation/) in the documentation. 20 | 21 | If you want to hack on `emacs-tree-sitter` itself, see the next section instead. 22 | 23 | ## Setup for Development 24 | **Note**: On Windows, use Powershell instead of Bash or cmd.exe. 25 | 26 | - Clone this repo with the `--recursive` flag. 27 | - Add 3 of its directories to `load-path`: `core/`, `lisp/` and `langs/`. 28 | - Install [eask](https://emacs-eask.github.io/). 29 | - Run `bin/setup`. 30 | 31 | If you want to hack on the high-level features (in Lisp) only: 32 | - Make changes to the `.el` files. 33 | - Add tests to `tree-sitter-tests.el` and run them with `bin/test`. 34 | 35 | If you want to build additional (or all) grammars from source, or work on the core dynamic module, see the next 2 sections. 36 | 37 | ### Building grammars from source 38 | 39 | **Note**: If you are only interested in building the grammar binaries, not the dynamic module, check out [tree-sitter-langs](https://github.com/emacs-tree-sitter/tree-sitter-langs#building-grammars-from-source). 40 | 41 | - Install NodeJS. It is needed to generate the grammar code from the JavaScript DSL. The recommended tool to manage NodeJS is [volta](https://volta.sh/). 42 | - Install [tree-sitter CLI tool](https://tree-sitter.github.io/tree-sitter/creating-parsers#installation): (Its binary can also be downloaded directly from [GitHub](https://github.com/tree-sitter/tree-sitter/releases).) **Note: version 0.20+ cannot be used, as they introduced [a breaking change](https://github.com/tree-sitter/tree-sitter/pull/1157) in binary storage location.** 43 | ```bash 44 | # For yarn user 45 | yarn global add tree-sitter-cli@0.19.3 46 | 47 | # For npm user 48 | npm install -g tree-sitter-cli@0.19.3 49 | ``` 50 | - Run `langs/script/compile`. For example: 51 | ```bash 52 | langs/script/compile rust 53 | ``` 54 | 55 | ### Working on the dynamic module 56 | 57 | - Install the [Rust toolchain](https://rustup.rs/). 58 | - Build: 59 | ```bash 60 | bin/build 61 | ``` 62 | - Test: 63 | ```bash 64 | bin/test 65 | ``` 66 | - Continuously rebuild and test on change (requires [cargo-watch](https://github.com/passcod/cargo-watch)): 67 | ```bash 68 | bin/test watch 69 | ``` 70 | 71 | To test against a different version of Emacs, set the environment variable `EMACS` (e.g. `EMACS=/snap/bin/emacs make test`). 72 | 73 | ## Alternatives 74 | 75 | - [Tree-sitter in Emacs's core](https://lists.gnu.org/archive/html/emacs-devel/2022-11/msg01443.html) (the future) 76 | - Binding through C instead of Rust: https://github.com/karlotness/tree-sitter.el (dormant) 77 | 78 | ## Contribution 79 | 80 | Contributions are welcomed. Please take a look at the [issue list](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues) for ideas, or [create a new issue](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/new) to describe any idea you have for improvement. 81 | 82 | For language-specific issues/features, please check out [tree-sitter-langs](https://github.com/emacs-tree-sitter/tree-sitter-langs) instead. 83 | 84 | Show respect and empathy towards others. Both technical empathy and general empathy are highly valued. 85 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # bin/build [-profile {release,debug}] [-target aarch64-apple-darwin] 4 | # 5 | # Params: 6 | # - profile: Defaults to release. 7 | # - target: To specify a cross build. Defaults to empty. 8 | # 9 | 10 | set -euo pipefail 11 | 12 | here=$(cd "$(dirname "$BASH_SOURCE")"; pwd) 13 | source "$here/env.bash" 14 | 15 | core_root="$PROJECT_ROOT/core" 16 | 17 | # Parse named parameters. 18 | POSITIONAL=() 19 | while [[ $# -gt 0 ]]; do 20 | key="$1" 21 | case $key in 22 | (-profile) 23 | profile_="$2" 24 | shift 25 | shift 26 | ;; 27 | (-target) 28 | target="$2" 29 | shift 30 | shift 31 | ;; 32 | (*) 33 | POSITIONAL+=("$1") 34 | shift 35 | ;; 36 | esac 37 | done 38 | profile_=${profile_:-release} 39 | target=${target:-''} 40 | 41 | # Construct arguments to cargo. 42 | extra_args=() 43 | target_dir=target 44 | case $profile_ in 45 | (release) 46 | extra_args+=(--release) 47 | ;; 48 | (debug) 49 | ;; 50 | (*) 51 | echo "!! Unknown profile $profile_" 52 | exit 1 53 | esac 54 | case $target in 55 | (aarch64-apple-darwin) 56 | extra_args+=(--target "$target") 57 | target_dir="target/$target" 58 | ;; 59 | ('') 60 | ;; 61 | (*) 62 | echo "!! Unsupport cross-build target $target" 63 | exit 1 64 | ;; 65 | esac 66 | 67 | echo "!! Building the dynamic module" 68 | ( 69 | cd "$core_root" 70 | cargo build --all "${extra_args[@]}" 71 | 72 | cp -f "$target_dir/$profile_/$MODULE_ORIGINAL" "$MODULE_RENAMED" 73 | version=$(cargo pkgid | cut -d'#' -f2 | cut -d: -f2 | cut -d@ -f2) 74 | echo "$version" | tr -d $'\n' > DYN-VERSION 75 | ) 76 | 77 | echo "!! Building Lisp code" 78 | if [[ $target != '' ]]; then 79 | echo "!! Will skip building Lisp since this is a cross build" 80 | exit 0 81 | fi 82 | ( 83 | cd "$core_root" 84 | eask compile --allow-error 85 | ) 86 | ( 87 | cd "$PROJECT_ROOT" 88 | eask compile --allow-error 89 | ) 90 | -------------------------------------------------------------------------------- /bin/build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$profile = "release" 3 | ) 4 | 5 | $here = $PSScriptRoot 6 | $project_root = (Get-Item $here).Parent.FullName 7 | $module_name = "tsc_dyn" 8 | $module_renamed = $module_name.replace("_", "-") 9 | $core_root = "$project_root\core" 10 | 11 | echo "!! Building the dynamic module" 12 | Push-Location $core_root 13 | try { 14 | switch ($profile) { 15 | 'debug' { cargo build --all } 16 | 'release' { cargo build --all --release } 17 | default { throw "Unknown profile $profile" } 18 | } 19 | 20 | Copy-Item "target\$profile\${module_name}.dll" "${module_renamed}.dll" 21 | $version = ((cargo pkgid) | Out-String).Trim().Split('#')[-1].Split(':')[-1].Split('@')[-1] 22 | Set-Content -Path "DYN-VERSION" -Value "${version}" -NoNewLine -Force 23 | eask compile --allow-error 24 | } finally { 25 | Pop-Location 26 | } 27 | 28 | echo "!! Building Lisp code" 29 | Push-Location $project_root 30 | try { 31 | eask compile --allow-error 32 | } finally { 33 | Pop-Location 34 | } 35 | -------------------------------------------------------------------------------- /bin/dev: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ":"; exec emacs -Q --script "$0" -- "$@" # -*-emacs-lisp-*- 3 | 4 | ;;; This file contains convenient dev commands that only need to work in macOS/Linux. (They may work 5 | ;;; in Windows, but that'would be incidental.) 6 | ;;; 7 | ;;; Usage: 8 | ;;; 9 | ;;; script/dev release-notes 10 | ;;; script/dev release-notes Unreleased 11 | ;;; 12 | ;;; script/dev current-version 13 | ;;; script/dev bump-version 14 | 15 | (eval-when-compile 16 | (require 'subr-x) 17 | (require 'cl-lib)) 18 | 19 | (defconst Cargo.toml "core/Cargo.toml") 20 | (defconst Cargo.lock "core/Cargo.lock") 21 | (defconst tsc.el "core/tsc.el") 22 | (defconst tree-sitter.el "lisp/tree-sitter.el") 23 | (defconst CHANGELOG.md "CHANGELOG.md") 24 | 25 | (defmacro -with-file-edits (file &rest body) 26 | (declare (indent 1)) 27 | `(with-temp-file ,file 28 | (message "Updating %s" ,file) 29 | (insert-file-contents ,file) 30 | ,@body)) 31 | 32 | (defmacro -with-file-contents (file &rest body) 33 | (declare (indent 1)) 34 | `(with-temp-buffer 35 | (insert-file-contents ,file) 36 | ,@body)) 37 | 38 | (defun -release-notes (&optional version) 39 | (let ((version (or version "Unreleased"))) 40 | (string-trim 41 | (-with-file-contents CHANGELOG.md 42 | (re-search-forward (format "## .*%s.*" version)) 43 | (forward-line) 44 | (beginning-of-line) 45 | (let ((start (point))) 46 | (re-search-forward "^##") 47 | (buffer-substring-no-properties start (1- (match-beginning 0)))))))) 48 | 49 | (defun -current-version () 50 | (-with-file-contents tree-sitter.el 51 | (re-search-forward "Version: \\(.*\\)") 52 | (match-string 1))) 53 | 54 | (defun -next-version (v) 55 | (let ((v (version-to-list v))) 56 | (cl-incf (caddr v)) 57 | (apply #'format "%s.%s.%s" v))) 58 | 59 | (defun -modify-regexp (regexp text) 60 | (re-search-forward regexp) 61 | (replace-match text nil nil nil 1)) 62 | 63 | (defun cmd:release-notes (&optional version) 64 | "Print VERSION's release notes, defaulting to 'Unreleased'." 65 | (princ (-release-notes version))) 66 | 67 | (defun cmd:bump-version (&optional next-or-flag) 68 | "Increase the version. This does not create a git tag. 69 | 70 | If NEXT-OR-FLAG is not given, increase the patch version. 71 | 72 | If NEXT-OR-FLAG is \"lisp-only\", only increase the version of the Lisp code. " 73 | (let* (lisp-only 74 | (rel-notes (-release-notes)) 75 | (current (-current-version)) 76 | (next (or (pcase next-or-flag 77 | ("lisp-only" (setq lisp-only t) nil) 78 | (_ next-or-flag)) 79 | (-next-version current))) 80 | (time (decode-time (current-time))) 81 | (date (format "%d-%02d-%02d" (nth 5 time) (nth 4 time) (nth 3 time)))) 82 | (unless (version< current next) 83 | (error "Next version must be greater than current, got '%s' -> '%s'" current next)) 84 | (unless (= 3 (length (version-to-list next))) 85 | (error "Next version must have the format .., got '%s'" next)) 86 | (if (string-empty-p rel-notes) 87 | (warn "Release notes are empty!") 88 | (message "Release notes:\n%s\n" rel-notes)) 89 | (message " Bumping version %s -> %s" current next) 90 | (unless lisp-only 91 | (-with-file-edits Cargo.toml 92 | (-modify-regexp "version *= *\"\\(.*\\)\"" next)) 93 | (-with-file-edits Cargo.lock 94 | (search-forward "name = \"emacs-tree-sitter\"") 95 | (-modify-regexp "version *= *\"\\(.*\\)\"" next))) 96 | (-with-file-edits tsc.el 97 | (-modify-regexp "Version: *\\(.*\\)" next) 98 | (unless lisp-only 99 | (-modify-regexp "tsc--dyn-version.*\"\\(.*\\)\"" next))) 100 | (-with-file-edits tree-sitter.el 101 | (-modify-regexp "Version: *\\(.*\\)" next) 102 | (-modify-regexp "Package-Requires:.*\(tsc *\"\\(.*\\)\"" next)) 103 | (-with-file-edits CHANGELOG.md 104 | (search-forward "[Unreleased]") 105 | (end-of-line) 106 | (insert (format "\n\n## [%s] - %s" next date)) 107 | (search-forward "[Unreleased]") 108 | (search-forward current) 109 | (replace-match next) 110 | (end-of-line) 111 | (insert (format "\n[%s]: https://github.com/emacs-tree-sitter/elisp-tree-sitter/compare/%s...%s" 112 | next current next))))) 113 | 114 | (defun cmd:current-version () 115 | "Print the current version." 116 | (princ (-current-version))) 117 | 118 | (defun cmd:generate-doc-md () 119 | "Generate markdown files from `org', for documentation." 120 | (let* ((root-dir (file-name-directory 121 | (directory-file-name 122 | (file-name-directory load-file-name)))) 123 | (doc-dir (format "%sdoc/" root-dir)) 124 | (ox-hugo-path (format "%sox-hugo" doc-dir)) 125 | (default-directory doc-dir)) 126 | (add-to-list 'load-path ox-hugo-path) 127 | (add-to-list 'load-path doc-dir) 128 | (require 'org) 129 | (require 'ox) 130 | (require 'ox-hugo) 131 | (load "-config-ox-hugo.el") 132 | (with-current-buffer (find-file "emacs-tree-sitter.org") 133 | (org-hugo-export-wim-to-md :all-subtrees)))) 134 | 135 | (defun -main (&rest args) 136 | (when-let* ((cmd (car args)) 137 | (func (intern (format "cmd:%s" cmd)))) 138 | (apply func (cdr args)))) 139 | 140 | (when (string= "--" (car argv)) 141 | (apply #'-main (cdr argv))) 142 | -------------------------------------------------------------------------------- /bin/ensure-lang: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # TODO: Move away from hacky bash scripts. 4 | 5 | # Usage: 6 | # ensure-lang 7 | # 8 | # Examples: 9 | # ensure-lang rust 10 | # ensure-lang python 11 | 12 | set -euo pipefail 13 | 14 | here=$(cd "$(dirname "$BASH_SOURCE")"; pwd) 15 | source "$here/env.bash" 16 | 17 | LANG=$1 18 | 19 | ( 20 | cd "$PROJECT_ROOT" 21 | 22 | $EMACS --batch \ 23 | --directory "$PROJECT_ROOT/core" \ 24 | --directory "$PROJECT_ROOT/lisp" \ 25 | --directory "$PROJECT_ROOT/langs" \ 26 | --eval " 27 | (progn 28 | (setq tree-sitter-langs--testing t) 29 | (require 'tree-sitter-langs) 30 | (tree-sitter-langs-ensure '$LANG))" 31 | ) 32 | -------------------------------------------------------------------------------- /bin/ensure-lang.ps1: -------------------------------------------------------------------------------- 1 | $here = $PSScriptRoot 2 | $project_root = (Get-Item $here).Parent.FullName 3 | $lang = $args[0] 4 | 5 | Push-Location -Path $project_root 6 | try { 7 | emacs --batch ` 8 | --directory "$project_root\core" ` 9 | --directory "$project_root\lisp" ` 10 | --directory "$project_root\langs" ` 11 | --eval "(progn (setq tree-sitter-langs--testing t) (require 'tree-sitter-langs) (tree-sitter-langs-ensure '$lang))" 12 | } finally { 13 | Pop-Location 14 | } 15 | -------------------------------------------------------------------------------- /bin/env.bash: -------------------------------------------------------------------------------- 1 | set -euo pipefail 2 | 3 | system=$(uname) 4 | if [[ $system == "Linux" ]]; then 5 | ext="so" 6 | elif [[ $system == "FreeBSD" ]]; then 7 | ext="so" 8 | elif [[ $system == "Darwin" ]]; then 9 | ext="dylib" 10 | else 11 | echo "Unsupported system: $system" 12 | exit 1 13 | fi 14 | 15 | here=$(cd "$(dirname "$BASH_SOURCE")"; pwd) 16 | 17 | PROJECT_ROOT=$(cd "$here/.."; pwd) 18 | export PROJECT_ROOT 19 | 20 | export MODULE_ORIGINAL=${MODULE_ORIGINAL:-libtsc_dyn.$ext} 21 | export MODULE_NAME=${MODULE_NAME:-tsc-dyn} 22 | export MODULE_RENAMED=${MODULE_NAME}.$ext 23 | export EMACS=${EMACS:-emacs} 24 | -------------------------------------------------------------------------------- /bin/inspect-binaries: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | here=$(cd "$(dirname "$BASH_SOURCE")"; pwd) 5 | PROJECT_ROOT=$(cd "$here/.."; pwd) 6 | 7 | ( 8 | cd "$PROJECT_ROOT" 9 | system=$(uname) 10 | 11 | case $system in 12 | (Linux) 13 | echo ┌───────────────────────────────────────────────────────────────────── 14 | echo └ Dynamic module: 15 | file core/tsc-dyn.so 16 | ldd core/tsc-dyn.so 17 | echo ┌───────────────────────────────────────────────────────────────────── 18 | echo └ Emacs: 19 | ldd "$(which emacs)" 20 | ;; 21 | (Darwin) 22 | echo ┌───────────────────────────────────────────────────────────────────── 23 | echo └ Dynamic module: 24 | file core/tsc-dyn.dylib 25 | otool -L core/tsc-dyn.dylib 26 | echo ┌───────────────────────────────────────────────────────────────────── 27 | echo └ Emacs: 28 | otool -L "$(which emacs)" 29 | ;; 30 | (*) 31 | echo "Unknown system $system" 32 | esac 33 | ) 34 | -------------------------------------------------------------------------------- /bin/inspect-binaries.ps1: -------------------------------------------------------------------------------- 1 | echo "TODO Figure out how to reliably determine MSVC toolchain paths." 2 | -------------------------------------------------------------------------------- /bin/package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | here=$(cd "$(dirname "$BASH_SOURCE")"; pwd) 6 | source "$here/env.bash" 7 | 8 | function eask-zip-tar { 9 | local name=$1 10 | local tar_file 11 | tar_file="dist/$name-$(eask version).tar" 12 | gzip --verbose "$tar_file" --stdout > "$tar_file".gz 13 | tar --gzip --list --file "$tar_file".gz 14 | } 15 | 16 | ( 17 | cd "$PROJECT_ROOT"/core 18 | eask compile 19 | eask package 20 | eask-zip-tar tsc 21 | ) 22 | 23 | ( 24 | cd "$PROJECT_ROOT" 25 | eask build 26 | eask package 27 | eask-zip-tar tree-sitter 28 | ) 29 | -------------------------------------------------------------------------------- /bin/package.ps1: -------------------------------------------------------------------------------- 1 | $here = $PSScriptRoot 2 | $project_root = (Get-Item $here).Parent.FullName 3 | $core_root = "$project_root\core" 4 | 5 | Push-Location $core_root 6 | try { 7 | eask compile 8 | eask package 9 | 10 | $version = ((eask version) | Out-String).Trim() 11 | $tar_file = "dist\tsc-$version.tar" 12 | gzip --verbose $tar_file --stdout > "$tar_file.gz" 13 | tar --gzip --list --file "$tar_file" 14 | } finally { 15 | Pop-Location 16 | } 17 | 18 | Push-Location $project_root 19 | try { 20 | eask compile 21 | eask package 22 | 23 | $version = ((eask version) | Out-String).Trim() 24 | $tar_file = "dist\tree-sitter-$version.tar" 25 | gzip --verbose $tar_file --stdout > "$tar_file.gz" 26 | tar --gzip --list --file "$tar_file" 27 | } finally { 28 | Pop-Location 29 | } 30 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | here=$(cd "$(dirname "$BASH_SOURCE")"; pwd) 6 | source "$here/env.bash" 7 | 8 | ( 9 | cd "$PROJECT_ROOT" 10 | # XXX: Create the directory because `eask link` doesn't. 11 | #mkdir -p "$(eask package-directory)" 12 | eask link add tsc core 13 | eask install 14 | ) 15 | -------------------------------------------------------------------------------- /bin/setup.ps1: -------------------------------------------------------------------------------- 1 | $here = $PSScriptRoot 2 | $project_root = (Get-Item $here).Parent.FullName 3 | 4 | Push-Location $project_root 5 | try { 6 | # XXX: Create the directory because `eask link` doesn't. 7 | #$eask_package_dir = ((eask package-directory) | Out-String).Trim() 8 | #New-Item -ItemType Directory -Force -Path "$eask_package_dir" 9 | eask link add tsc core 10 | eask install 11 | } finally { 12 | Pop-Location 13 | } 14 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | here=$(cd "$(dirname "$BASH_SOURCE")"; pwd) 6 | source "$here/env.bash" 7 | 8 | echo "[!!] Using Emacs binary from $(which $EMACS) version: $($EMACS --version)" 9 | if [[ $* == "watch" ]]; then 10 | ( 11 | cd "$PROJECT_ROOT/core" 12 | cargo watch -s ../bin/build -s ../bin/test 13 | ) 14 | else 15 | if [[ $* == "integ" ]]; then 16 | test_mod="tsc-dyn-get-tests.el" 17 | elif [[ $* == "bench" ]]; then 18 | test_mod="tree-sitter-bench.el" 19 | else 20 | test_mod="tree-sitter-tests.el" 21 | fi 22 | ( 23 | cd "$PROJECT_ROOT" 24 | eask emacs --batch \ 25 | --directory "$PROJECT_ROOT/core" \ 26 | --directory "$PROJECT_ROOT/lisp" \ 27 | --directory "$PROJECT_ROOT/langs" \ 28 | --directory "$PROJECT_ROOT/tests" \ 29 | -l ert \ 30 | -l "$test_mod" \ 31 | -f ert-run-tests-batch-and-exit 32 | ) 33 | fi 34 | -------------------------------------------------------------------------------- /bin/test.ps1: -------------------------------------------------------------------------------- 1 | $here = $PSScriptRoot 2 | $project_root = (Get-Item $here).Parent.FullName 3 | 4 | if ($args[0] -eq "watch") { 5 | Push-Location "$project_root\core" 6 | try { 7 | cargo watch -s "powershell -noprofile ..\bin\build.ps1" -s "powershell -noprofile ..\bin\test.ps1" 8 | } finally { 9 | Pop-Location 10 | } 11 | } else { 12 | if ($args[0] -eq "integ") { 13 | $test_mod = "tsc-dyn-get-tests.el" 14 | } elseif ($args[0] -eq "bench") { 15 | $test_mod = "tree-sitter-bench.el" 16 | } else { 17 | $test_mod = "tree-sitter-tests.el" 18 | } 19 | # XXX: It seems that Emacs writes to stderr, so PowerShell thinks it's an error. Redirecting to 20 | # stdout alone doesn't help, because it's the processed stderr, which contain error records, not 21 | # the original stderr. Piping at the end to convert these error records into strings doesn't 22 | # work either. 23 | # 24 | # It's worth noting that the issue happens only on Azure Pipelines, with Windows 2019, probably 25 | # because of the execution mode being remote or something. 26 | # 27 | # https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/ 28 | # https://github.com/PowerShell/JEA/issues/24 29 | # https://github.com/PowerShell/PowerShell/issues/4002 30 | # https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide 31 | $ErrorActionPreference = 'Continue' 32 | emacs --version 33 | Push-Location $project_root 34 | try { 35 | eask emacs --batch ` 36 | --directory "$project_root\core" ` 37 | --directory "$project_root\lisp" ` 38 | --directory "$project_root\langs" ` 39 | --directory "$project_root\tests" ` 40 | -l ert ` 41 | -l "$test_mod" ` 42 | -f ert-run-tests-batch-and-exit 43 | } finally { 44 | Pop-Location 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | # Avoid dynamic linking to vcruntime*.dll, which requires users to install VC++ redistributable 3 | # package. See https://github.com/ubolonton/emacs-tree-sitter/issues/86. 4 | rustflags = ["-C", "target-feature=+crt-static"] 5 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | 3 | tsc-dyn.* 4 | DYN-VERSION 5 | target/ 6 | -------------------------------------------------------------------------------- /core/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | 3 | max_width = 100 4 | use_small_heuristics = "Max" 5 | 6 | newline_style = "Unix" 7 | 8 | use_field_init_shorthand = true 9 | use_try_shorthand = true 10 | 11 | # I'd like to order these by specificity. 12 | reorder_imports = false 13 | reorder_modules = false 14 | -------------------------------------------------------------------------------- /core/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.51" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" 19 | 20 | [[package]] 21 | name = "cc" 22 | version = "1.0.72" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 25 | 26 | [[package]] 27 | name = "cfg-if" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 31 | 32 | [[package]] 33 | name = "ctor" 34 | version = "0.1.21" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" 37 | dependencies = [ 38 | "quote", 39 | "syn", 40 | ] 41 | 42 | [[package]] 43 | name = "darling" 44 | version = "0.10.2" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" 47 | dependencies = [ 48 | "darling_core", 49 | "darling_macro", 50 | ] 51 | 52 | [[package]] 53 | name = "darling_core" 54 | version = "0.10.2" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" 57 | dependencies = [ 58 | "fnv", 59 | "ident_case", 60 | "proc-macro2", 61 | "quote", 62 | "strsim", 63 | "syn", 64 | ] 65 | 66 | [[package]] 67 | name = "darling_macro" 68 | version = "0.10.2" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" 71 | dependencies = [ 72 | "darling_core", 73 | "quote", 74 | "syn", 75 | ] 76 | 77 | [[package]] 78 | name = "emacs" 79 | version = "0.18.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "6797a940189d353de79bec32abe717aeeecd79a08236e84404c888354e040665" 82 | dependencies = [ 83 | "anyhow", 84 | "ctor", 85 | "emacs-macros", 86 | "emacs_module", 87 | "once_cell", 88 | "rustc_version", 89 | "thiserror", 90 | ] 91 | 92 | [[package]] 93 | name = "emacs-macros" 94 | version = "0.17.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "69656fdfe7c2608b87164964db848b5c3795de7302e3130cce7131552c6be161" 97 | dependencies = [ 98 | "darling", 99 | "proc-macro2", 100 | "quote", 101 | "syn", 102 | ] 103 | 104 | [[package]] 105 | name = "emacs-tree-sitter" 106 | version = "0.18.0" 107 | dependencies = [ 108 | "emacs", 109 | "libloading", 110 | "once_cell", 111 | "tree-sitter", 112 | ] 113 | 114 | [[package]] 115 | name = "emacs_module" 116 | version = "0.18.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "b3067bc974045ed2c6db333bd4fc30d3bdaafa6421a9a889fa7b2826b6f7f2fa" 119 | 120 | [[package]] 121 | name = "fnv" 122 | version = "1.0.7" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 125 | 126 | [[package]] 127 | name = "ident_case" 128 | version = "1.0.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 131 | 132 | [[package]] 133 | name = "libloading" 134 | version = "0.7.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" 137 | dependencies = [ 138 | "cfg-if", 139 | "winapi", 140 | ] 141 | 142 | [[package]] 143 | name = "memchr" 144 | version = "2.4.1" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 147 | 148 | [[package]] 149 | name = "once_cell" 150 | version = "1.8.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 153 | 154 | [[package]] 155 | name = "proc-macro2" 156 | version = "1.0.33" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" 159 | dependencies = [ 160 | "unicode-xid", 161 | ] 162 | 163 | [[package]] 164 | name = "quote" 165 | version = "1.0.10" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 168 | dependencies = [ 169 | "proc-macro2", 170 | ] 171 | 172 | [[package]] 173 | name = "regex" 174 | version = "1.5.4" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 177 | dependencies = [ 178 | "aho-corasick", 179 | "memchr", 180 | "regex-syntax", 181 | ] 182 | 183 | [[package]] 184 | name = "regex-syntax" 185 | version = "0.6.25" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 188 | 189 | [[package]] 190 | name = "rustc_version" 191 | version = "0.2.3" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 194 | dependencies = [ 195 | "semver", 196 | ] 197 | 198 | [[package]] 199 | name = "semver" 200 | version = "0.9.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 203 | dependencies = [ 204 | "semver-parser", 205 | ] 206 | 207 | [[package]] 208 | name = "semver-parser" 209 | version = "0.7.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 212 | 213 | [[package]] 214 | name = "strsim" 215 | version = "0.9.3" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 218 | 219 | [[package]] 220 | name = "syn" 221 | version = "1.0.82" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 224 | dependencies = [ 225 | "proc-macro2", 226 | "quote", 227 | "unicode-xid", 228 | ] 229 | 230 | [[package]] 231 | name = "thiserror" 232 | version = "1.0.30" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 235 | dependencies = [ 236 | "thiserror-impl", 237 | ] 238 | 239 | [[package]] 240 | name = "thiserror-impl" 241 | version = "1.0.30" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 244 | dependencies = [ 245 | "proc-macro2", 246 | "quote", 247 | "syn", 248 | ] 249 | 250 | [[package]] 251 | name = "tree-sitter" 252 | version = "0.20.0" 253 | source = "git+https://github.com/ubolonton/tree-sitter?branch=improve-text-provider#475b822f47bdc58d832533448b6f6d9818554f37" 254 | dependencies = [ 255 | "cc", 256 | "regex", 257 | ] 258 | 259 | [[package]] 260 | name = "unicode-xid" 261 | version = "0.2.2" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 264 | 265 | [[package]] 266 | name = "winapi" 267 | version = "0.3.9" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 270 | dependencies = [ 271 | "winapi-i686-pc-windows-gnu", 272 | "winapi-x86_64-pc-windows-gnu", 273 | ] 274 | 275 | [[package]] 276 | name = "winapi-i686-pc-windows-gnu" 277 | version = "0.4.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 280 | 281 | [[package]] 282 | name = "winapi-x86_64-pc-windows-gnu" 283 | version = "0.4.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 286 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "emacs-tree-sitter" 3 | version = "0.18.0" 4 | authors = ["Tuấn-Anh Nguyễn "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [lib] 9 | path = "src/lib.rs" 10 | name = "tsc_dyn" 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | emacs = "0.18" 15 | libloading = "0.7.0" 16 | tree-sitter = "0.20.0" 17 | once_cell = "1.7.2" 18 | 19 | [profile.release] 20 | opt-level = 3 21 | lto = "thin" 22 | 23 | [profile.release.build-override] 24 | opt-level = 0 25 | 26 | [patch.crates-io.tree-sitter] 27 | git = "https://github.com/ubolonton/tree-sitter" 28 | branch = "improve-text-provider" 29 | -------------------------------------------------------------------------------- /core/Eask: -------------------------------------------------------------------------------- 1 | (package "tsc" 2 | "0.18.0" 3 | "Core Tree-sitter APIs") 4 | 5 | (website-url "https://github.com/emacs-tree-sitter/elisp-tree-sitter") 6 | (keywords "languages" "tools" "parsers" "dynamic-modules" "tree-sitter") 7 | 8 | (package-file "tsc.el") 9 | (files 10 | "*.el" 11 | "tsc-dyn.dylib" 12 | "tsc-dyn.so" 13 | "tsc-dyn.dll" 14 | "Cargo.toml" 15 | "Cargo.lock" 16 | "src") 17 | 18 | (script "test" "echo \"Error: no test specified\" && exit 1") 19 | 20 | (depends-on "emacs" "27.1") 21 | 22 | (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 23 | 24 | ;; Use GNU tar in Windows 25 | (when (memq system-type '(cygwin windows-nt ms-dos)) 26 | (setq package-build-tar-executable "C:/Program Files/Git/usr/bin/tar.exe")) 27 | -------------------------------------------------------------------------------- /core/src/error.rs: -------------------------------------------------------------------------------- 1 | emacs::define_errors! { 2 | tsc_error "Tree-sitter core error" 3 | 4 | tsc_lang_load_failed "Language load failed" (tsc_error) 5 | tsc_lang_abi_error "Language's ABI is incompatible" (tsc_error) 6 | tsc_lang_abi_too_old "Language's ABI is too old" (tsc_lang_load_failed tsc_lang_abi_error) 7 | tsc_lang_abi_too_new "Language's ABI is too new" (tsc_lang_load_failed tsc_lang_abi_error) 8 | 9 | tsc_invalid_ranges "Invalid parsing ranges" (tsc_error) 10 | 11 | tsc_query_invalid "Invalid query" (tsc_error) 12 | tsc_query_invalid_syntax "Query syntax error" (tsc_query_invalid) 13 | tsc_query_invalid_node_type "Query contains invalid node type" (tsc_query_invalid) 14 | tsc_query_invalid_field "Query contains invalid field name" (tsc_query_invalid) 15 | tsc_query_invalid_capture "Query contains undeclared capture name" (tsc_query_invalid) 16 | tsc_query_invalid_predicate "Query contains invalid predicate usage" (tsc_query_invalid) 17 | tsc_query_invalid_structure "Query contains invalid pattern structure" (tsc_query_invalid) 18 | } 19 | -------------------------------------------------------------------------------- /core/src/lang.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, os, collections::HashMap, sync::Mutex}; 2 | 3 | use emacs::{defun, Result, ResultExt, GlobalRef, Value, Env, IntoLisp, FromLisp, ErrorKind}; 4 | 5 | use libloading::{Library, Symbol}; 6 | use once_cell::sync::Lazy; 7 | 8 | use crate::{types, error}; 9 | use tree_sitter::{LANGUAGE_VERSION, MIN_COMPATIBLE_LANGUAGE_VERSION}; 10 | 11 | #[derive(Copy, Clone)] 12 | #[repr(transparent)] 13 | pub struct Language(pub(crate) tree_sitter::Language); 14 | 15 | impl IntoLisp<'_> for Language { 16 | fn into_lisp(self, env: &Env) -> Result { 17 | // Safety: Language has the same representation as the opaque pointer type. 18 | let ptr: *mut os::raw::c_void = unsafe { mem::transmute(self) }; 19 | // Safety: The finalizer does nothing. 20 | unsafe { env.make_user_ptr(Some(no_op::), ptr) } 21 | } 22 | } 23 | 24 | impl FromLisp<'_> for Language { 25 | fn from_lisp(value: Value) -> Result { 26 | match value.get_user_finalizer()? { 27 | Some(fin) if fin == no_op:: => { 28 | let ptr = value.get_user_ptr()?; 29 | // Safety: Language has the same representation as the opaque pointer type. 30 | Ok(unsafe { mem::transmute(ptr) }) 31 | } 32 | _ => Err(ErrorKind::WrongTypeUserPtr { expected: "TreeSitterLanguage" }.into()) 33 | } 34 | } 35 | } 36 | 37 | impl_newtype_traits!(Language); 38 | 39 | impl_pred!(language_p, Language); 40 | 41 | impl Language { 42 | pub fn id(self) -> usize { 43 | unsafe { mem::transmute(self) } 44 | } 45 | 46 | pub fn info(self) -> &'static LangInfo { 47 | // TODO: Explain the safety. 48 | LANG_INFOS.try_lock().expect("Failed to access language info registry") 49 | .get(&self.id()) 50 | .map(|info| unsafe { types::erase_lifetime(info) }) 51 | .expect("Failed to get language info from the registry") 52 | } 53 | } 54 | 55 | unsafe extern "C" fn no_op(_: *mut os::raw::c_void) {} 56 | 57 | // ------------------------------------------------------------------------------------------------- 58 | 59 | pub struct LangInfo { 60 | load_file: String, 61 | lang_symbol: GlobalRef, 62 | _lib: Library, 63 | node_types: Vec, 64 | field_names: Vec, 65 | } 66 | 67 | impl LangInfo { 68 | #[inline] 69 | pub(crate) fn node_type(&self, id: u16) -> Option<&GlobalRef> { 70 | self.node_types.get(id as usize) 71 | } 72 | 73 | #[inline] 74 | pub(crate) fn field_name(&self, id: u16) -> Option<&GlobalRef> { 75 | if id == 0 { 76 | None 77 | } else { 78 | self.field_names.get(id as usize - 1) 79 | } 80 | } 81 | } 82 | 83 | // TODO: Consider optimizing for accessing language's metadata, i.e. making Language a big wrapper 84 | // around tree_sitter::Language, so that hash lookup happens only when returning the language of a 85 | // parser/tree/node/query. 86 | static LANG_INFOS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); 87 | 88 | /// Load the shared lib FILE and return the language under SYMBOL-NAME. 89 | /// The language's name symbol is set to LANG-SYMBOL. 90 | #[defun] 91 | fn _load_language(file: String, symbol_name: String, lang_symbol: Value) -> Result { 92 | let env = lang_symbol.env; 93 | let lib = unsafe { Library::new(&file) }.or_signal(env, error::tsc_lang_load_failed)?; 94 | let tree_sitter_lang: Symbol<'_, unsafe extern "C" fn() -> _> = 95 | unsafe { lib.get(symbol_name.as_bytes()) }.or_signal(env, error::tsc_lang_load_failed)?; 96 | let language: tree_sitter::Language = unsafe { tree_sitter_lang() }; 97 | let version = language.version(); 98 | if version < MIN_COMPATIBLE_LANGUAGE_VERSION { 99 | return env.signal(error::tsc_lang_abi_too_old, ( 100 | version, supported_abi_range(env)?, file 101 | )); 102 | } 103 | if version > LANGUAGE_VERSION { 104 | return env.signal(error::tsc_lang_abi_too_new, ( 105 | version, supported_abi_range(env)?, file 106 | )); 107 | } 108 | let node_types = (0..language.node_kind_count() as u16).map(|id| { 109 | let type_str = language.node_kind_for_id(id).expect("Failed to get node type for id"); 110 | let value = if !language.node_kind_is_visible(id) { 111 | env.intern(&format!(":{}", type_str)).expect("Failed to intern keyword for node type") 112 | } else if language.node_kind_is_named(id) { 113 | env.intern(type_str).expect("Failed to intern symbol for node type") 114 | } else { 115 | type_str.into_lisp(env).expect("Failed to make string for node type") 116 | }; 117 | value.make_global_ref() 118 | }).collect(); 119 | let field_names = (1..=language.field_count() as u16).map(|id| { 120 | let field_str = language.field_name_for_id(id).expect("Failed to get field name for id"); 121 | env.intern(&format!(":{}", field_str)).expect("Failed to intern keyword for field name") 122 | .make_global_ref() 123 | }).collect(); 124 | let language: Language = language.into(); 125 | LANG_INFOS.try_lock().expect("Failed to access language info registry") 126 | .insert(language.id(), LangInfo { 127 | load_file: file, 128 | lang_symbol: lang_symbol.make_global_ref(), 129 | _lib: lib, 130 | node_types, 131 | field_names, 132 | }); 133 | Ok(language) 134 | } 135 | 136 | /// Return LANGUAGE's name, as a symbol. 137 | #[defun] 138 | fn _lang_symbol(language: Language) -> Result<&'static GlobalRef> { 139 | Ok(&language.info().lang_symbol) 140 | } 141 | 142 | /// Return the shared lib file that LANGUAGE was loaded from. 143 | #[defun] 144 | fn _lang_load_file(language: Language) -> Result<&'static String> { 145 | Ok(&language.info().load_file) 146 | } 147 | 148 | /// Return the node type associated with the numeric TYPE-ID in LANGUAGE. 149 | /// 150 | /// For named nodes, the node type is a symbol. For example: 'identifier, 'block. 151 | /// For anonymous nodes, the node type is a string. For example: "if", "else". 152 | /// For auxiliary (invisible) nodes, the node type is a keyword. For example: :end, :_expression. 153 | #[defun] 154 | fn lang_node_type(language: Language, type_id: u16) -> Result> { 155 | Ok(language.info().node_type(type_id)) 156 | } 157 | 158 | /// Return a field's name keyword, given its numeric FIELD-ID in LANGUAGE. 159 | #[defun] 160 | fn lang_field(language: Language, field_id: u16) -> Result> { 161 | Ok(language.info().field_name(field_id)) 162 | } 163 | 164 | /// Return the numeric id of TYPE-NAME in LANGUAGE. 165 | #[defun] 166 | fn _lang_type_id_for_name(language: Language, type_name: String, named: Option) -> Result { 167 | Ok(language.0.id_for_node_kind(&type_name, named.is_some())) 168 | } 169 | 170 | /// Return the range of language ABI's that this module can load. 171 | #[defun] 172 | fn supported_abi_range(env: &Env) -> Result { 173 | env.cons(MIN_COMPATIBLE_LANGUAGE_VERSION, LANGUAGE_VERSION) 174 | } 175 | 176 | macro_rules! defun_lang_methods { 177 | ($($(#[$meta:meta])* $($lisp_name:literal)? fn $name:ident $( ( $( $param:ident : $type:ty ),* ) )? -> $rtype:ty )*) => { 178 | $( 179 | #[defun$((name = $lisp_name))?] 180 | $(#[$meta])* 181 | fn $name(language: Language, $( $( $param : $type ),* )? ) -> Result<$rtype> { 182 | Ok((language.0).$name( $( $( $param ),* )? )) 183 | } 184 | )* 185 | }; 186 | } 187 | 188 | defun_lang_methods! { 189 | /// Return the ABI version number for LANGUAGE. 190 | /// This version number is used to ensure that languages were generated by a 191 | /// compatible version of tree-sitter. `tsc-set-language' will fail if the language 192 | /// is incompatible, so there's rarely a need to use this function, except for 193 | /// debugging purposes. 194 | "lang-version" fn version -> usize 195 | 196 | /// Return the number of distinct node types defined in LANGUAGE. 197 | "lang-count-types" fn node_kind_count -> usize 198 | 199 | /// Return the number of distinct field names defined in LANGUAGE. 200 | "lang-count-fields" fn field_count -> usize 201 | 202 | /// Return t if the numeric TYPE-ID identifies a named node type in LANGUAGE. 203 | "lang-node-type-named-p" fn node_kind_is_named(type_id: u16) -> bool 204 | 205 | /// Return the numeric id of FIELD-NAME in LANGUAGE. 206 | "-lang-field-id-for-name" fn field_id_for_name(field_name: String) -> Option 207 | } 208 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | use emacs::{Env, Result}; 3 | 4 | #[macro_use] 5 | mod types; 6 | mod error; 7 | mod lang; 8 | mod parser; 9 | mod tree; 10 | mod node; 11 | mod cursor; 12 | mod query; 13 | 14 | emacs::plugin_is_GPL_compatible! {} 15 | 16 | #[emacs::module(name = "tsc-dyn", defun_prefix = "tsc", mod_in_name = false)] 17 | fn init(env: &Env) -> Result<()> { 18 | env.call("set", (env.intern("tsc-dyn--version")?, option_env!("CARGO_PKG_VERSION")))?; 19 | Ok(()) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /core/src/node.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::{Ref, RefCell, RefMut}, 3 | mem, 4 | ops::{Deref, DerefMut}, 5 | }; 6 | 7 | use emacs::{defun, Env, IntoLisp, Result, Value, GlobalRef}; 8 | use tree_sitter::{InputEdit, Node, Tree}; 9 | 10 | use crate::{ 11 | types::{self, BytePos, Point, Shared, Range}, 12 | lang::Language, 13 | }; 14 | 15 | // ------------------------------------------------------------------------------------------------- 16 | 17 | /// Wrapper around `tree_sitter::Node` that can have 'static lifetime, by keeping a ref-counted 18 | /// reference to the underlying tree. 19 | #[derive(Clone)] 20 | pub struct RNode { 21 | tree: Shared, 22 | inner: Node<'static>, 23 | } 24 | 25 | impl_pred!(node_p, &RefCell); 26 | 27 | pub struct RNodeBorrow<'e> { 28 | #[allow(unused)] 29 | reft: Ref<'e, Tree>, 30 | node: &'e Node<'e>, 31 | } 32 | 33 | impl<'e> Deref for RNodeBorrow<'e> { 34 | type Target = Node<'e>; 35 | 36 | #[inline] 37 | fn deref(&self) -> &Self::Target { 38 | self.node 39 | } 40 | } 41 | 42 | pub struct RNodeBorrowMut<'e> { 43 | #[allow(unused)] 44 | reft: RefMut<'e, Tree>, 45 | node: Node<'e>, 46 | } 47 | 48 | impl<'e> Deref for RNodeBorrowMut<'e> { 49 | type Target = Node<'e>; 50 | 51 | #[inline] 52 | fn deref(&self) -> &Self::Target { 53 | &self.node 54 | } 55 | } 56 | 57 | impl<'e> DerefMut for RNodeBorrowMut<'e> { 58 | #[inline] 59 | fn deref_mut(&mut self) -> &mut Self::Target { 60 | &mut self.node 61 | } 62 | } 63 | 64 | impl PartialEq for RNode { 65 | fn eq(&self, other: &Self) -> bool { 66 | self.inner == other.inner 67 | } 68 | } 69 | 70 | impl IntoLisp<'_> for RNode { 71 | fn into_lisp(self, env: &Env) -> Result { 72 | RefCell::new(self).into_lisp(env) 73 | } 74 | } 75 | 76 | impl RNode { 77 | pub fn new<'e, F: FnOnce(&'e Tree) -> Node<'e>>(tree: Shared, f: F) -> Self { 78 | let rtree = unsafe { types::erase_lifetime(&*tree.borrow()) }; 79 | let inner = unsafe { mem::transmute(f(rtree)) }; 80 | Self { tree, inner } 81 | } 82 | 83 | pub fn clone_tree(&self) -> Shared { 84 | self.tree.clone() 85 | } 86 | 87 | pub fn map<'e, F: FnOnce(&Node<'e>) -> Node<'e>>(&self, f: F) -> Self { 88 | Self::new(self.clone_tree(), |_| f(&self.inner)) 89 | } 90 | 91 | #[inline] 92 | pub fn borrow(&self) -> RNodeBorrow { 93 | let reft = self.tree.borrow(); 94 | let node = &self.inner; 95 | RNodeBorrow { reft, node } 96 | } 97 | 98 | #[inline] 99 | pub fn borrow_mut(&mut self) -> RNodeBorrowMut { 100 | let reft = self.tree.borrow_mut(); 101 | let node = self.inner; 102 | RNodeBorrowMut { reft, node } 103 | } 104 | } 105 | 106 | // ------------------------------------------------------------------------------------------------- 107 | 108 | /// Exposes methods that return a node's property. 109 | macro_rules! defun_node_props { 110 | ($($(#[$meta:meta])* $($lisp_name:literal)? fn $name:ident -> $type:ty $(; $into:ident)? )*) => { 111 | $( 112 | #[defun$((name = $lisp_name))?] 113 | $(#[$meta])* 114 | fn $name(node: &RNode) -> Result<$type> { 115 | Ok(node.borrow().$name()$(.$into())?) 116 | } 117 | )* 118 | }; 119 | } 120 | 121 | /// Exposes methods that return another node. 122 | macro_rules! defun_node_navs { 123 | ($($(#[$meta:meta])* $($lisp_name:literal)? fn $name:ident $( ( $( $param:ident $($into:ident)? : $type:ty ),* ) )?)*) => { 124 | $( 125 | #[defun$((name = $lisp_name))?] 126 | $(#[$meta])* 127 | fn $name(node: &RNode, $( $( $param : $type ),* )? ) -> Result> { 128 | Ok(node.borrow().$name( $( $( $param $(.$into())? ),* )? ).map(|other| { 129 | node.map(|_| other) 130 | })) 131 | } 132 | )* 133 | }; 134 | } 135 | 136 | emacs::use_symbols!(ERROR); 137 | 138 | /// Return NODE's type, as a symbol (named node), or a string (anonymous node). 139 | /// 140 | /// If NODE is a named node, its type is a symbol. For example: 'identifier, 'block. 141 | /// If NODE is an anonymous node, its type is a string. For example: "if", "else". 142 | #[defun] 143 | fn node_type(node: &RNode) -> Result<&'static GlobalRef> { 144 | Ok(node.borrow().lisp_type()) 145 | } 146 | 147 | pub(crate) trait LispUtils { 148 | fn lisp_type(&self) -> &'static GlobalRef; 149 | fn lisp_byte_range<'e>(&self, env: &'e Env) -> Result>; 150 | fn lisp_start_byte(&self) -> BytePos; 151 | fn lisp_end_byte(&self) -> BytePos; 152 | fn lisp_start_point(&self) -> Point; 153 | fn lisp_end_point(&self) -> Point; 154 | fn lisp_range(&self) -> Range; 155 | } 156 | 157 | impl<'n> LispUtils for Node<'n> { 158 | #[inline] 159 | fn lisp_type(&self) -> &'static GlobalRef { 160 | let language: Language = self.language().into(); 161 | if self.is_error() { 162 | ERROR 163 | } else { 164 | &language.info().node_type(self.kind_id()).expect("Failed to get node type from id") 165 | } 166 | } 167 | 168 | #[inline] 169 | fn lisp_byte_range<'e>(&self, env: &'e Env) -> Result> { 170 | let beg: BytePos = self.start_byte().into(); 171 | let end: BytePos = self.end_byte().into(); 172 | env.cons(beg, end) 173 | } 174 | 175 | #[inline] 176 | fn lisp_start_byte(&self) -> BytePos { 177 | self.start_byte().into() 178 | } 179 | 180 | #[inline] 181 | fn lisp_end_byte(&self) -> BytePos { 182 | self.end_byte().into() 183 | } 184 | 185 | #[inline] 186 | fn lisp_start_point(&self) -> Point { 187 | self.start_position().into() 188 | } 189 | 190 | #[inline] 191 | fn lisp_end_point(&self) -> Point { 192 | self.end_position().into() 193 | } 194 | 195 | #[inline] 196 | fn lisp_range(&self) -> Range { 197 | self.range().into() 198 | }} 199 | 200 | defun_node_props! { 201 | /// Return NODE's numeric type-id. 202 | "node-type-id" fn kind_id -> u16 203 | 204 | // Predicates ---------------------------------------------------------------------------------- 205 | 206 | /// Return t if NODE is 'named'. 207 | /// Named nodes correspond to named rules in the grammar, whereas anonymous nodes 208 | /// correspond to string literals in the grammar. 209 | "node-named-p" fn is_named -> bool 210 | 211 | /// Return t if NODE is 'extra'. 212 | /// Extra nodes represent things like comments, which are not required the grammar, 213 | /// but can appear anywhere. 214 | "node-extra-p" fn is_extra -> bool 215 | 216 | /// Return t if NODE represents a syntax error. 217 | /// Syntax errors represent parts of the code that could not be incorporated into a 218 | /// valid syntax tree. 219 | "node-error-p" fn is_error -> bool 220 | 221 | /// Return t if NODE is 'missing'. 222 | /// Missing nodes are inserted by the parser in order to recover from certain kinds 223 | /// of syntax errors. 224 | "node-missing-p" fn is_missing -> bool 225 | 226 | /// Return t if NODE has been edited. 227 | "node-has-changes-p" fn has_changes -> bool 228 | 229 | /// Return t if NODE represents a syntax error or contains any syntax errors. 230 | "node-has-error-p" fn has_error -> bool 231 | 232 | // Position ------------------------------------------------------------------------------------ 233 | 234 | /// Return NODE's start byte position. 235 | "node-start-byte" fn start_byte -> BytePos; into 236 | 237 | /// Return NODE's start point, in the form of (LINE-NUMBER . BYTE-COLUMN). 238 | "node-start-point" fn start_position -> Point; into 239 | 240 | /// Return NODE's end byte position. 241 | "node-end-byte" fn end_byte -> BytePos; into 242 | 243 | /// Return NODE's end point, in the form of (LINE-NUMBER . BYTE-COLUMN). 244 | "node-end-point" fn end_position -> Point; into 245 | 246 | /// Return a vector of NODE's [START-BYTEPOS END-BYTEPOS START-POINT END-POINT]. 247 | "node-range" fn range -> Range; into 248 | 249 | // Counting child nodes ------------------------------------------------------------------------ 250 | 251 | /// Return NODE's number of children. 252 | "count-children" fn child_count -> usize 253 | 254 | /// Return NODE's number of named children. 255 | "count-named-children" fn named_child_count -> usize 256 | } 257 | 258 | /// Return NODE's (START-BYTEPOS . END-BYTEPOS). 259 | #[defun] 260 | fn node_byte_range<'e>(env: &'e Env, node: &RNode) -> Result> { 261 | node.borrow().lisp_byte_range(env) 262 | } 263 | 264 | /// Return t if two nodes are identical. 265 | #[defun] 266 | fn node_eq(node1: &RNode, node2: &RNode) -> Result { 267 | Ok(node1 == node2) 268 | } 269 | 270 | /// Apply FUNCTION to each of NODE's children, for side effects only. 271 | #[defun] 272 | fn mapc_children(function: Value, node: &RNode) -> Result<()> { 273 | let inner = node.borrow(); 274 | // TODO: Reuse cursor. 275 | let cursor = &mut inner.walk(); 276 | for child in inner.children(cursor) { 277 | let child = node.map(|_| child); 278 | function.call((child,))?; 279 | } 280 | Ok(()) 281 | } 282 | 283 | // TODO: named_children. 284 | // TODO: children_by_field_name. 285 | // TODO: children_by_field_id. 286 | 287 | defun_node_navs! { 288 | /// Return NODE's parent node. 289 | "get-parent" fn parent 290 | 291 | // Child --------------------------------------------------------------------------------------- 292 | 293 | /// Return NODE's child at the given 0-based index. 294 | "get-nth-child" fn child(i: usize) 295 | 296 | /// Return NODE's named child at the given 0-based index. 297 | "get-nth-named-child" fn named_child(i: usize) 298 | 299 | /// Return NODE's child with the given FIELD-NAME string. 300 | "-get-child-by-field-name" fn child_by_field_name(field_name: String) 301 | 302 | /// Return NODE's child with the given numerical FIELD-ID. 303 | "get-child-by-field-id" fn child_by_field_id(field_id: u16) 304 | 305 | // Sibling ------------------------------------------------------------------------------------- 306 | 307 | /// Return NODE's next sibling. 308 | "get-next-sibling" fn next_sibling 309 | 310 | /// Return NODE's previous sibling. 311 | "get-prev-sibling" fn prev_sibling 312 | 313 | /// Return NODE's next named sibling. 314 | "get-next-named-sibling" fn next_named_sibling 315 | 316 | /// Return NODE's previous named sibling. 317 | "get-prev-named-sibling" fn prev_named_sibling 318 | 319 | // Descendant ---------------------------------------------------------------------------------- 320 | 321 | /// Return the smallest node within NODE that spans the given range of byte 322 | /// positions. 323 | "get-descendant-for-byte-range" fn descendant_for_byte_range(start into: BytePos, end into: BytePos) 324 | 325 | /// Return the smallest node within NODE that spans the given point range. 326 | "get-descendant-for-point-range" fn descendant_for_point_range(start into: Point, end into: Point) 327 | 328 | /// Return the smallest named node within NODE that spans the given range of byte 329 | /// positions. 330 | "get-named-descendant-for-byte-range" fn named_descendant_for_byte_range(start into: BytePos, end into: BytePos) 331 | 332 | /// Return the smallest named node within NODE that spans the given point range. 333 | "get-named-descendant-for-point-range" fn named_descendant_for_point_range(start into: Point, end into: Point) 334 | } 335 | 336 | defun_node_props! { 337 | /// Return the sexp representation of NODE, in a string. 338 | "node-to-sexp" fn to_sexp -> String 339 | } 340 | 341 | /// Edit NODE to keep it in sync with source code that has been edited. 342 | /// 343 | /// You must describe the edit both in terms of byte positions and in terms of 344 | /// (LINE-NUMBER . BYTE-COLUMN) coordinates. 345 | /// 346 | /// LINE-NUMBER should be the number returned by `line-number-at-pos', which counts 347 | /// from 1. 348 | /// 349 | /// BYTE-COLUMN should count from 0, like Emacs's `current-column'. However, unlike 350 | /// that function, it should count bytes, instead of displayed glyphs. 351 | /// 352 | /// This function is only rarely needed. When you edit a syntax tree, all of the 353 | /// nodes that you retrieve from the tree afterward will already reflect the edit. 354 | /// You only need to use this function when you have a node that you want to keep 355 | /// and continue to use after an edit. 356 | #[defun] 357 | fn edit_node( 358 | node: &mut RNode, 359 | start_bytepos: BytePos, 360 | old_end_bytepos: BytePos, 361 | new_end_bytepos: BytePos, 362 | start_point: Point, 363 | old_end_point: Point, 364 | new_end_point: Point, 365 | ) -> Result<()> { 366 | let edit = InputEdit { 367 | start_byte: start_bytepos.into(), 368 | old_end_byte: old_end_bytepos.into(), 369 | new_end_byte: new_end_bytepos.into(), 370 | start_position: start_point.into(), 371 | old_end_position: old_end_point.into(), 372 | new_end_position: new_end_point.into(), 373 | }; 374 | node.borrow_mut().edit(&edit); 375 | Ok(()) 376 | } 377 | -------------------------------------------------------------------------------- /core/src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use emacs::{defun, Result, Value, Vector, Env, ResultExt}; 4 | use tree_sitter::{Parser, Tree}; 5 | 6 | use crate::{ 7 | types::{BytePos, Point, Range, Shared}, 8 | lang::Language, 9 | error, 10 | }; 11 | 12 | fn shared(t: T) -> Shared { 13 | Rc::new(RefCell::new(t)) 14 | } 15 | 16 | impl_pred!(parser_p, &RefCell); 17 | 18 | /// Create a new parser. 19 | #[defun(user_ptr)] 20 | fn make_parser() -> Result { 21 | Ok(Parser::new()) 22 | } 23 | 24 | /// Set the LANGUAGE that PARSER should use for parsing. 25 | /// 26 | /// This may fail if there was a version mismatch: the loaded LANGUAGE was generated 27 | /// with an incompatible version of tree-sitter-cli. 28 | #[defun] 29 | fn set_language(parser: &mut Parser, language: Language, env: &Env) -> Result<()> { 30 | parser.set_language(language.into()).or_signal(env, error::tsc_lang_abi_error) 31 | } 32 | 33 | /// Return PARSER's current language. 34 | #[defun(mod_in_name = true)] 35 | fn language(parser: &Parser) -> Result> { 36 | Ok(parser.language().map(|l| l.into())) 37 | } 38 | 39 | // TODO: Add a version that reuses a single byte buffer to avoid multiple allocations. Also allow 40 | // `parse` to pass a soft size limit to the input function. 41 | 42 | // TODO: Add parse_buffer. 43 | 44 | /// Parse source code chunks generated by INPUT-FUNCTION with PARSER; return a tree. 45 | /// 46 | /// INPUT-FUNCTION should take 3 parameters: (BYTEPOS LINE-NUMBER BYTE-COLUMN), and 47 | /// return a fragment of the source code, starting from the position identified by 48 | /// either BYTEPOS or (LINE-NUMBER . BYTE-COLUMN). It should return an empty string 49 | /// to signal the end of the source code. 50 | /// 51 | /// BYTEPOS is Emacs's 1-based byte position. 52 | /// 53 | /// LINE-NUMBER is the number returned by `line-number-at-pos', which counts from 1. 54 | /// 55 | /// BYTE-COLUMN counts from 0, likes Emacs's `current-column'. However, unlike that 56 | /// function, it counts bytes, instead of displayed glyphs. 57 | /// 58 | /// If you have already parsed an earlier version of this document, and it has since 59 | /// been edited, pass the previously parsed OLD-TREE so that its unchanged parts can 60 | /// be reused. This will save time and memory. For this to work correctly, you must 61 | /// have already edited it using `tsc-edit-tree' function in a way that exactly 62 | /// matches the source code changes. 63 | #[defun] 64 | fn parse_chunks(parser: &mut Parser, input_function: Value, old_tree: Option<&Shared>) -> Result> { 65 | let old_tree = match old_tree { 66 | Some(v) => Some(v.try_borrow()?), 67 | _ => None, 68 | }; 69 | let old_tree = match &old_tree { 70 | Some(r) => Some(&**r), 71 | _ => None, 72 | }; 73 | // This is used to hold potential error, because the callback cannot return a Result, and 74 | // unwinding across FFI boundary during a panic is UB (future Rust versions will abort). 75 | // See https://github.com/rust-lang/rust/issues/52652. 76 | let mut input_error = None; 77 | let input = &mut |byte: usize, point: tree_sitter::Point| -> String { 78 | let bytepos: BytePos = byte.into(); 79 | let point: Point = point.into(); 80 | input_function.call((bytepos, point.line_number(), point.byte_column())) 81 | .and_then(|v| v.into_rust()) 82 | .unwrap_or_else(|e| { 83 | input_error = Some(e); 84 | "".to_owned() 85 | }) 86 | }; 87 | // TODO: Support error cases (None). 88 | let tree = parser.parse_with(input, old_tree).unwrap(); 89 | match input_error { 90 | None => Ok(shared(tree)), 91 | Some(e) => Err(e), 92 | } 93 | } 94 | 95 | /// Use PARSER to parse the INPUT string, returning a tree. 96 | #[defun] 97 | fn parse_string(parser: &mut Parser, input: String) -> Result> { 98 | let tree = parser.parse(input, None).unwrap(); 99 | Ok(shared(tree)) 100 | } 101 | 102 | /// Instruct PARSER to start the next parse from the beginning. 103 | /// 104 | /// If PARSER previously failed because of a timeout or a cancellation, then by 105 | /// default, it will resume where it left off on the next parse. If you don't want 106 | /// to resume, and instead intend to use PARSER to parse some other code, you must 107 | /// call this function first. 108 | /// 109 | /// Note: timeout and cancellation are not yet properly supported. 110 | #[defun] 111 | fn _reset_parser(parser: &mut Parser) -> Result<()> { 112 | Ok(parser.reset()) 113 | } 114 | 115 | /// Return the duration in microseconds that PARSER is allowed to take each parse. 116 | /// Note: timeout and cancellation are not yet properly supported. 117 | #[defun] 118 | fn _timeout_micros(parser: &Parser) -> Result { 119 | Ok(parser.timeout_micros()) 120 | } 121 | 122 | /// Set MAX-DURATION in microseconds that PARSER is allowed to take each parse. 123 | /// Note: timeout and cancellation are not yet properly supported. 124 | #[defun] 125 | fn _set_timeout_micros(parser: &mut Parser, max_duration: u64) -> Result<()> { 126 | Ok(parser.set_timeout_micros(max_duration)) 127 | } 128 | 129 | /// Set the RANGES of text that PARSER should include when parsing. 130 | /// 131 | /// By default, PARSER will always include entire documents. This function allows 132 | /// you to parse only a portion of a document but still return a syntax tree whose 133 | /// ranges match up with the document as a whole. RANGES should be a vector, and can 134 | /// be disjointed. 135 | /// 136 | /// This is useful for parsing multi-language documents. 137 | #[defun] 138 | fn set_included_ranges(parser: &mut Parser, ranges: Vector) -> Result<()> { 139 | let len = ranges.len(); 140 | let included = &mut Vec::with_capacity(len); 141 | for i in 0..len { 142 | let range: Range = ranges.get(i)?; 143 | included.push(range.into()); 144 | } 145 | parser.set_included_ranges(included).or_else(|error| { 146 | ranges.value().env.signal(error::tsc_invalid_ranges, (error.0, )) 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /core/src/query.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, iter}; 2 | 3 | use emacs::{defun, Env, Error, GlobalRef, IntoLisp, Result, Value, Vector}; 4 | use tree_sitter::{Node, QueryCursor, QueryErrorKind, TextProvider}; 5 | 6 | use crate::{ 7 | types::{BytePos, Point}, 8 | lang::Language, 9 | node::{RNode, LispUtils}, 10 | error, 11 | }; 12 | 13 | fn vec_to_vector<'e, T: IntoLisp<'e>>(env: &'e Env, vec: Vec) -> Result> { 14 | let vector = env.make_vector(vec.len(), ())?; 15 | for (i, v) in vec.into_iter().enumerate() { 16 | vector.set(i, v)?; 17 | } 18 | Ok(vector) 19 | } 20 | 21 | // ------------------------------------------------------------------------------------------------- 22 | // Query 23 | 24 | struct Query { 25 | pub(crate) raw: tree_sitter::Query, 26 | pub(crate) capture_tags: Vec, 27 | } 28 | 29 | impl_pred!(query_p, &RefCell); 30 | 31 | /// Create a new query from a SOURCE containing one or more S-expression patterns. 32 | /// 33 | /// The query is associated with LANGUAGE, and can only be run on syntax nodes 34 | /// parsed with LANGUAGE. 35 | /// 36 | /// TAG-ASSIGNER is a function that is called to determine how captures are tagged 37 | /// in query results. It should take a capture name defined in SOURCE's patterns 38 | /// (e.g. "function.builtin"), and return a tag value. If the return value is nil, 39 | /// the associated capture name is disabled. 40 | #[defun(user_ptr)] 41 | fn _make_query(language: Language, source: String, tag_assigner: Value) -> Result { 42 | let mut raw = tree_sitter::Query::new(language.into(), &source).or_else(|err| { 43 | let symbol = match err.kind { 44 | QueryErrorKind::Syntax => error::tsc_query_invalid_syntax, 45 | QueryErrorKind::NodeType => error::tsc_query_invalid_node_type, 46 | QueryErrorKind::Field => error::tsc_query_invalid_field, 47 | QueryErrorKind::Capture => error::tsc_query_invalid_capture, 48 | QueryErrorKind::Predicate => error::tsc_query_invalid_predicate, 49 | QueryErrorKind::Structure => error::tsc_query_invalid_structure, 50 | QueryErrorKind::Language => error::tsc_lang_abi_error, 51 | }; 52 | let byte_pos: BytePos = err.offset.into(); 53 | let point: Point = tree_sitter::Point { row: err.row, column: err.column }.into(); 54 | // TODO: Character position? 55 | // TODO: Convert named node types and field names to symbols and keywords? 56 | tag_assigner.env.signal(symbol, (err.message, point, byte_pos)) 57 | })?; 58 | let capture_names = raw.capture_names().to_vec(); 59 | let mut capture_tags = vec![]; 60 | for name in &capture_names { 61 | let value = tag_assigner.call((name, ))?; 62 | if !value.is_not_nil() { 63 | raw.disable_capture(name); 64 | } 65 | capture_tags.push(value.make_global_ref()) 66 | } 67 | Ok(Query { raw, capture_tags }) 68 | } 69 | 70 | macro_rules! defun_query_methods { 71 | ($($(#[$meta:meta])* $($lisp_name:literal)? fn $name:ident $( ( $( $param:ident : $type:ty ),* ) )? -> $rtype:ty $(; $into:ident)? )*) => { 72 | $( 73 | #[defun$((name = $lisp_name))?] 74 | $(#[$meta])* 75 | fn $name(query: &Query, $( $( $param : $type ),* )? ) -> Result<$rtype> { 76 | Ok(query.raw.$name( $( $( $param ),* )? )$(.$into())?) 77 | } 78 | )* 79 | }; 80 | } 81 | 82 | defun_query_methods! { 83 | /// Return the byte position where the NTH pattern starts in QUERY's source. 84 | "-query-start-byte-for-pattern" fn start_byte_for_pattern(nth: usize) -> BytePos; into 85 | 86 | /// Return the number of patterns in QUERY. 87 | "query-count-patterns" fn pattern_count -> usize 88 | } 89 | 90 | /// Return the names of the captures used in QUERY. 91 | #[defun] 92 | fn _query_capture_names(query: Value) -> Result { 93 | let env = query.env; 94 | let query = query.into_ref::()?; 95 | let names = query.raw.capture_names(); 96 | let vec = env.make_vector(names.len(), ())?; 97 | for (i, name) in names.iter().enumerate() { 98 | vec.set(i, name)?; 99 | } 100 | Ok(vec) 101 | } 102 | 103 | /// Return all of QUERY's available capture tags. 104 | /// See `tsc-make-query' for an explanation of capture tagging. 105 | #[defun(mod_in_name = true)] 106 | fn capture_tags<'e>(env: &'e Env, query: &Query) -> Result> { 107 | let symbols = env.make_vector(query.capture_tags.len(), ())?; 108 | for (i, symbol) in query.capture_tags.iter().enumerate() { 109 | symbols.set(i, symbol)?; 110 | } 111 | Ok(symbols) 112 | } 113 | 114 | /// Disable a certain capture within QUERY, by specifying its NAME. 115 | /// 116 | /// This prevents the capture from being returned in matches, and also avoids any 117 | /// resource usage associated with recording the capture. 118 | #[defun] 119 | fn _disable_capture(query: &mut Query, name: String) -> Result<()> { 120 | query.raw.disable_capture(&name); 121 | Ok(()) 122 | } 123 | 124 | // ------------------------------------------------------------------------------------------------- 125 | // QueryCursor 126 | 127 | impl_pred!(query_cursor_p, &RefCell); 128 | 129 | /// Create a new cursor for executing a given query. 130 | /// 131 | /// The cursor stores the state that is needed to iteratively search for matches. 132 | #[defun(user_ptr)] 133 | fn make_query_cursor() -> Result { 134 | Ok(QueryCursor::new()) 135 | } 136 | 137 | fn text_callback<'e>( 138 | text_function: Value<'e>, 139 | error: &'e RefCell>, 140 | ) -> impl TextProvider<'e> { 141 | move |child: Node| { 142 | let beg = child.lisp_start_byte(); 143 | let end = child.lisp_end_byte(); 144 | let text = text_function.call((beg, end)).and_then(|v| v.into_rust()).unwrap_or_else(|e| { 145 | error.borrow_mut().replace(e); 146 | "".to_owned() 147 | }); 148 | iter::once(text.into_bytes()) 149 | } 150 | } 151 | 152 | #[defun] 153 | fn _query_cursor_matches<'e>( 154 | cursor: &mut QueryCursor, 155 | query: &Query, 156 | node: &RNode, 157 | text_function: Value<'e>, 158 | ) -> Result> { 159 | let raw = &query.raw; 160 | let error = RefCell::new(None); 161 | let matches = cursor.matches( 162 | raw, 163 | node.borrow().clone(), 164 | text_callback(text_function, &error), 165 | ); 166 | let mut vec = vec![]; 167 | let env = text_function.env; 168 | for m in matches { 169 | if let Some(error) = error.borrow_mut().take() { 170 | return Err(error); 171 | } 172 | let captures = env.make_vector(m.captures.len(), ())?; 173 | for (ci, c) in m.captures.iter().enumerate() { 174 | let captured_node = node.map(|_| c.node); 175 | let capture = env.cons( 176 | &query.capture_tags[c.index as usize], 177 | captured_node 178 | )?; 179 | captures.set(ci, capture)?; 180 | } 181 | let _match = env.cons(m.pattern_index, captures)?; 182 | vec.push(_match); 183 | } 184 | vec_to_vector(env, vec) 185 | } 186 | 187 | // TODO: Make _query_cursor_captures accept a `capture_type` instead, e.g. node type, byte range. 188 | #[defun] 189 | fn _query_cursor_captures_1<'e>( 190 | cursor: &mut QueryCursor, 191 | query: Value<'e>, 192 | node: &RNode, 193 | text_function: Value<'e>, 194 | ) -> Result> { 195 | let query = query.into_rust::<&RefCell>()?.borrow(); 196 | let raw = &query.raw; 197 | let error = RefCell::new(None); 198 | let captures = cursor.captures( 199 | raw, 200 | node.borrow().clone(), 201 | text_callback(text_function, &error), 202 | ); 203 | let mut vec = vec![]; 204 | let env = text_function.env; 205 | for (m, capture_index) in captures { 206 | if let Some(error) = error.borrow_mut().take() { 207 | return Err(error); 208 | } 209 | let c = m.captures[capture_index]; 210 | let capture = env.cons( 211 | &query.capture_tags[c.index as usize], 212 | c.node.lisp_byte_range(env)?, 213 | )?; 214 | vec.push((m.pattern_index, capture)); 215 | } 216 | // Prioritize captures from earlier patterns. 217 | vec.sort_unstable_by_key(|(i, _)| *i); 218 | let vector = env.make_vector(vec.len(), ())?; 219 | for (i, (_, v)) in vec.into_iter().enumerate() { 220 | vector.set(i, v)?; 221 | } 222 | Ok(vector) 223 | } 224 | 225 | #[defun] 226 | fn _query_cursor_captures<'e>( 227 | cursor: &mut QueryCursor, 228 | query: Value<'e>, 229 | node: &RNode, 230 | text_function: Value<'e>, 231 | ) -> Result> { 232 | let query = query.into_rust::<&RefCell>()?.borrow(); 233 | let raw = &query.raw; 234 | let error = RefCell::new(None); 235 | let captures = cursor.captures( 236 | raw, 237 | node.borrow().clone(), 238 | text_callback(text_function, &error), 239 | ); 240 | let mut vec = vec![]; 241 | let env = text_function.env; 242 | for (m, capture_index) in captures { 243 | if let Some(error) = error.borrow_mut().take() { 244 | return Err(error); 245 | } 246 | let c = m.captures[capture_index]; 247 | let captured_node = node.map(|_| c.node); 248 | let capture = env.cons( 249 | &query.capture_tags[c.index as usize], 250 | captured_node 251 | )?; 252 | vec.push(capture); 253 | } 254 | 255 | // XXX 256 | let vector = env.make_vector(vec.len(), ())?; 257 | for (i, v) in vec.into_iter().enumerate() { 258 | vector.set(i, v)?; 259 | } 260 | Ok(vector) 261 | } 262 | 263 | /// Limit CURSOR's query executions to the range of byte positions, from BEG to END. 264 | #[defun] 265 | fn _query_cursor_set_byte_range(cursor: &mut QueryCursor, beg: BytePos, end: BytePos) -> Result<()> { 266 | cursor.set_byte_range(beg.into()..end.into()); 267 | Ok(()) 268 | } 269 | 270 | /// Limit CURSOR's query executions to the point range, from BEG to END. 271 | /// 272 | /// A "point" in this context is a (LINE-NUMBER . BYTE-COLUMN) pair. See 273 | /// `tsc-parse-chunks' for a more detailed explanation. 274 | #[defun] 275 | fn _query_cursor_set_point_range(cursor: &mut QueryCursor, beg: Point, end: Point) -> Result<()> { 276 | cursor.set_point_range(beg.into()..end.into()); 277 | Ok(()) 278 | } 279 | -------------------------------------------------------------------------------- /core/src/tree.rs: -------------------------------------------------------------------------------- 1 | use emacs::{defun, Value, Result, Vector}; 2 | 3 | use tree_sitter::{InputEdit, Tree}; 4 | 5 | use crate::{ 6 | types::{Shared, BytePos, Point, Range}, 7 | lang::Language, 8 | node::RNode, 9 | }; 10 | 11 | // XXX: If we pass a &, #[defun] will assume it's refcell-wrapped. If we pass a Value, we need 12 | // .into_rust() boilerplate. This is a trick to avoid both. 13 | pub(crate) type Borrowed<'e, T> = &'e Shared; 14 | 15 | impl_pred!(tree_p, &Shared); 16 | 17 | /// Return the language that was used to parse the syntax TREE. 18 | #[defun(mod_in_name = true)] 19 | fn language(tree: Borrowed) -> Result { 20 | Ok(tree.borrow().language().into()) 21 | } 22 | 23 | /// Return the sexp representation of the syntax TREE, in a string. 24 | #[defun(mod_in_name = true)] 25 | fn to_sexp(tree: Borrowed) -> Result { 26 | Ok(tree.borrow().root_node().to_sexp()) 27 | } 28 | 29 | /// Return the root node of the syntax TREE. 30 | #[defun] 31 | fn root_node(tree: Borrowed) -> Result { 32 | Ok(RNode::new(tree.clone(), |tree| tree.root_node())) 33 | } 34 | 35 | /// Edit the syntax TREE to keep it in sync with source code that has been edited. 36 | /// 37 | /// You must describe the edit both in terms of byte positions and in terms of 38 | /// (LINE-NUMBER . BYTE-COLUMN) coordinates. 39 | /// 40 | /// LINE-NUMBER should be the number returned by `line-number-at-pos', which counts 41 | /// from 1. 42 | /// 43 | /// BYTE-COLUMN should count from 0, like Emacs's `current-column'. However, unlike 44 | /// that function, it should count bytes, instead of displayed glyphs. 45 | #[defun] 46 | fn edit_tree( 47 | tree: Borrowed, 48 | start_bytepos: BytePos, 49 | old_end_bytepos: BytePos, 50 | new_end_bytepos: BytePos, 51 | start_point: Point, 52 | old_end_point: Point, 53 | new_end_point: Point, 54 | ) -> Result<()> { 55 | let edit = InputEdit { 56 | start_byte: start_bytepos.into(), 57 | old_end_byte: old_end_bytepos.into(), 58 | new_end_byte: new_end_bytepos.into(), 59 | start_position: start_point.into(), 60 | old_end_position: old_end_point.into(), 61 | new_end_position: new_end_point.into(), 62 | }; 63 | tree.borrow_mut().edit(&edit); 64 | Ok(()) 65 | } 66 | 67 | /// Compare an edited OLD-TREE to NEW-TREE, both representing the same document. 68 | /// 69 | /// This function returns a sequence of ranges whose syntactic structure has changed. 70 | /// 71 | /// For this to work correctly, OLD-TREE must have been edited such that its ranges 72 | /// match up to NEW-TREE. Generally, you'll want to call this function right after 73 | /// calling one of the parsing functions, passing in the old tree that was passed 74 | /// as a parameter and the new tree that was returned. 75 | #[defun] 76 | fn changed_ranges<'e>(old_tree: Value<'e>, new_tree: Borrowed<'e, Tree>) -> Result> { 77 | let env = old_tree.env; 78 | let old_tree = old_tree.into_rust::>()?.borrow(); 79 | let new_tree = new_tree.borrow(); 80 | // TODO: Add a test to show that order is importance. 81 | let ranges = old_tree.changed_ranges(&*new_tree); 82 | let vec = env.make_vector(ranges.len(), ())?; 83 | for (i, range) in ranges.enumerate() { 84 | vec.set(i, Range(range))?; 85 | } 86 | Ok(vec) 87 | } 88 | 89 | /// Create a shallow copy of the syntax TREE. 90 | /// 91 | /// This is not very useful currently, as Emacs Lisp threads are subjected to a GIL. 92 | #[defun] 93 | fn _clone_tree(tree: Borrowed) -> Result> { 94 | Ok(tree.clone()) 95 | } 96 | -------------------------------------------------------------------------------- /core/src/types.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | mem, 3 | cell::RefCell, 4 | rc::Rc, 5 | }; 6 | 7 | use emacs::{defun, Env, FromLisp, IntoLisp, Result, Value, Vector}; 8 | 9 | pub type Shared = Rc>; 10 | 11 | pub unsafe fn erase_lifetime<'t, T>(x: &'t T) -> &'static T { 12 | mem::transmute(x) 13 | } 14 | 15 | macro_rules! impl_pred { 16 | ($name:ident, $type:ty) => { 17 | #[defun] 18 | fn $name(value: Value) -> Result { 19 | Ok(value.into_rust::<$type>().is_ok()) 20 | } 21 | }; 22 | } 23 | 24 | macro_rules! impl_newtype_traits { 25 | ($newtype:ty, $inner:ty) => { 26 | impl From<$inner> for $newtype { 27 | #[inline(always)] 28 | fn from(inner: $inner) -> Self { 29 | Self(inner) 30 | } 31 | } 32 | 33 | impl Into<$inner> for $newtype { 34 | #[inline(always)] 35 | fn into(self) -> $inner { 36 | self.0 37 | } 38 | } 39 | }; 40 | ($name:ident) => { 41 | impl_newtype_traits!($name, tree_sitter::$name); 42 | }; 43 | } 44 | 45 | // ------------------------------------------------------------------------------------------------- 46 | // Point 47 | 48 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 49 | pub struct Point(tree_sitter::Point); 50 | 51 | impl_pred!(point_p, Point); 52 | 53 | impl_newtype_traits!(Point); 54 | 55 | impl IntoLisp<'_> for Point { 56 | fn into_lisp(self, env: &Env) -> Result { 57 | env.cons(self.line_number(), self.byte_column()) 58 | } 59 | } 60 | 61 | impl FromLisp<'_> for Point { 62 | fn from_lisp(value: Value) -> Result { 63 | let row = value.car::()? - 1; 64 | let column = value.cdr()?; 65 | Ok(tree_sitter::Point { row, column }.into()) 66 | } 67 | } 68 | 69 | impl Point { 70 | #[inline(always)] 71 | pub(crate) fn line_number(&self) -> usize { 72 | self.0.row + 1 73 | } 74 | 75 | #[inline(always)] 76 | pub(crate) fn byte_column(&self) -> usize { 77 | self.0.column 78 | } 79 | } 80 | 81 | // ------------------------------------------------------------------------------------------------- 82 | // Emacs Byte Position (1-based, which is different from byte offset, which is 0-based). 83 | 84 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 85 | pub struct BytePos(usize); 86 | 87 | impl From for BytePos { 88 | #[inline(always)] 89 | fn from(byte_offset: usize) -> Self { 90 | Self(byte_offset + 1) 91 | } 92 | } 93 | 94 | impl Into for BytePos { 95 | #[inline(always)] 96 | fn into(self) -> usize { 97 | self.0 - 1 98 | } 99 | } 100 | 101 | impl FromLisp<'_> for BytePos { 102 | #[inline(always)] 103 | fn from_lisp(value: Value) -> Result { 104 | value.into_rust().map(Self) 105 | } 106 | } 107 | 108 | impl IntoLisp<'_> for BytePos { 109 | #[inline(always)] 110 | fn into_lisp(self, env: &Env) -> Result { 111 | self.0.into_lisp(env) 112 | } 113 | } 114 | 115 | // ------------------------------------------------------------------------------------------------- 116 | // Range 117 | 118 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 119 | pub struct Range(pub(crate) tree_sitter::Range); 120 | 121 | impl_pred!(range_p, Range); 122 | 123 | impl_newtype_traits!(Range); 124 | 125 | impl IntoLisp<'_> for Range { 126 | fn into_lisp(self, env: &Env) -> Result { 127 | let inner = self.0; 128 | let start_byte_pos: BytePos = inner.start_byte.into(); 129 | let end_byte_pos: BytePos = inner.end_byte.into(); 130 | env.vector(( 131 | start_byte_pos, 132 | end_byte_pos, 133 | Point(inner.start_point), 134 | Point(inner.end_point), 135 | )) 136 | } 137 | } 138 | 139 | impl FromLisp<'_> for Range { 140 | fn from_lisp(value: Value) -> Result { 141 | let vector: Vector = value.into_rust()?; 142 | let start_byte = vector.get::(0)?.into(); 143 | let end_byte = vector.get::(1)?.into(); 144 | let start_point = vector.get::(2)?.into(); 145 | let end_point = vector.get::(3)?.into(); 146 | Ok(tree_sitter::Range { start_byte, end_byte, start_point, end_point }.into()) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /core/tsc-obsolete.el: -------------------------------------------------------------------------------- 1 | ;;; tsc-obsolete.el --- Obsolete tree-sitter APIs -*- lexical-binding: t; coding: utf-8-*- 2 | 3 | ;; Copyright (C) 2020-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; This file contains obsolete `tsc' functions, kept around for temporary 12 | ;; backward compatibility. They will eventually be removed. 13 | 14 | ;;; Code: 15 | 16 | ;;; Public. 17 | (define-obsolete-function-alias 'ts-changed-ranges 'tsc-changed-ranges "2020-10-13") 18 | (define-obsolete-function-alias 'ts-count-children 'tsc-count-children "2020-10-13") 19 | (define-obsolete-function-alias 'ts-count-named-children 'tsc-count-named-children "2020-10-13") 20 | (define-obsolete-function-alias 'ts-current-field 'tsc-current-field "2020-10-13") 21 | (define-obsolete-function-alias 'ts-current-field-id 'tsc-current-field-id "2020-10-13") 22 | (define-obsolete-function-alias 'ts-current-node 'tsc-current-node "2020-10-13") 23 | (define-obsolete-function-alias 'ts-cursor-p 'tsc-cursor-p "2020-10-13") 24 | (define-obsolete-function-alias 'ts-dyn-get 'tsc-dyn-get "2020-10-13") 25 | (define-obsolete-function-alias 'ts-dyn-get-ensure 'tsc-dyn-get-ensure "2020-10-13") 26 | (define-obsolete-function-alias 'ts-edit-node 'tsc-edit-node "2020-10-13") 27 | (define-obsolete-function-alias 'ts-edit-tree 'tsc-edit-tree "2020-10-13") 28 | (define-obsolete-function-alias 'ts-get-child-by-field 'tsc-get-child-by-field "2020-10-13") 29 | (define-obsolete-function-alias 'ts-get-child-by-field-id 'tsc-get-child-by-field-id "2020-10-13") 30 | (define-obsolete-function-alias 'ts-get-descendant-for-byte-range 'tsc-get-descendant-for-byte-range "2020-10-13") 31 | (define-obsolete-function-alias 'ts-get-descendant-for-point-range 'tsc-get-descendant-for-point-range "2020-10-13") 32 | (define-obsolete-function-alias 'ts-get-descendant-for-position-range 'tsc-get-descendant-for-position-range "2020-10-13") 33 | (define-obsolete-function-alias 'ts-get-named-descendant-for-byte-range 'tsc-get-named-descendant-for-byte-range "2020-10-13") 34 | (define-obsolete-function-alias 'ts-get-named-descendant-for-point-range 'tsc-get-named-descendant-for-point-range "2020-10-13") 35 | (define-obsolete-function-alias 'ts-get-named-descendant-for-position-range 'tsc-get-named-descendant-for-position-range "2020-10-13") 36 | (define-obsolete-function-alias 'ts-get-next-named-sibling 'tsc-get-next-named-sibling "2020-10-13") 37 | (define-obsolete-function-alias 'ts-get-next-sibling 'tsc-get-next-sibling "2020-10-13") 38 | (define-obsolete-function-alias 'ts-get-nth-child 'tsc-get-nth-child "2020-10-13") 39 | (define-obsolete-function-alias 'ts-get-nth-named-child 'tsc-get-nth-named-child "2020-10-13") 40 | (define-obsolete-function-alias 'ts-get-parent 'tsc-get-parent "2020-10-13") 41 | (define-obsolete-function-alias 'ts-get-prev-named-sibling 'tsc-get-prev-named-sibling "2020-10-13") 42 | (define-obsolete-function-alias 'ts-get-prev-sibling 'tsc-get-prev-sibling "2020-10-13") 43 | (define-obsolete-function-alias 'ts-goto-first-child 'tsc-goto-first-child "2020-10-13") 44 | (define-obsolete-function-alias 'ts-goto-first-child-for-byte 'tsc-goto-first-child-for-byte "2020-10-13") 45 | (define-obsolete-function-alias 'ts-goto-first-child-for-position 'tsc-goto-first-child-for-position "2020-10-13") 46 | (define-obsolete-function-alias 'ts-goto-next-sibling 'tsc-goto-next-sibling "2020-10-13") 47 | (define-obsolete-function-alias 'ts-goto-parent 'tsc-goto-parent "2020-10-13") 48 | (define-obsolete-function-alias 'ts-lang-count-fields 'tsc-lang-count-fields "2020-10-13") 49 | (define-obsolete-function-alias 'ts-lang-count-types 'tsc-lang-count-types "2020-10-13") 50 | (define-obsolete-function-alias 'ts-lang-field 'tsc-lang-field "2020-10-13") 51 | (define-obsolete-function-alias 'ts-lang-field-id 'tsc-lang-field-id "2020-10-13") 52 | (define-obsolete-function-alias 'ts-lang-node-type 'tsc-lang-node-type "2020-10-13") 53 | (define-obsolete-function-alias 'ts-lang-node-type-id 'tsc-lang-node-type-id "2020-10-13") 54 | (define-obsolete-function-alias 'ts-lang-node-type-named-p 'tsc-lang-node-type-named-p "2020-10-13") 55 | (define-obsolete-function-alias 'ts-lang-version 'tsc-lang-version "2020-10-13") 56 | (define-obsolete-function-alias 'ts-language-p 'tsc-language-p "2020-10-13") 57 | (define-obsolete-function-alias 'ts-make-cursor 'tsc-make-cursor "2020-10-13") 58 | (define-obsolete-function-alias 'ts-make-parser 'tsc-make-parser "2020-10-13") 59 | (define-obsolete-function-alias 'ts-make-query 'tsc-make-query "2020-10-13") 60 | (define-obsolete-function-alias 'ts-make-query-cursor 'tsc-make-query-cursor "2020-10-13") 61 | (define-obsolete-function-alias 'ts-mapc-children 'tsc-mapc-children "2020-10-13") 62 | (define-obsolete-function-alias 'ts-node-byte-range 'tsc-node-byte-range "2020-10-13") 63 | (define-obsolete-function-alias 'ts-node-end-byte 'tsc-node-end-byte "2020-10-13") 64 | (define-obsolete-function-alias 'ts-node-end-point 'tsc-node-end-point "2020-10-13") 65 | (define-obsolete-function-alias 'ts-node-end-position 'tsc-node-end-position "2020-10-13") 66 | (define-obsolete-function-alias 'ts-node-eq 'tsc-node-eq "2020-10-13") 67 | (define-obsolete-function-alias 'ts-node-error-p 'tsc-node-error-p "2020-10-13") 68 | (define-obsolete-function-alias 'ts-node-extra-p 'tsc-node-extra-p "2020-10-13") 69 | (define-obsolete-function-alias 'ts-node-has-changes-p 'tsc-node-has-changes-p "2020-10-13") 70 | (define-obsolete-function-alias 'ts-node-has-error-p 'tsc-node-has-error-p "2020-10-13") 71 | (define-obsolete-function-alias 'ts-node-missing-p 'tsc-node-missing-p "2020-10-13") 72 | (define-obsolete-function-alias 'ts-node-named-p 'tsc-node-named-p "2020-10-13") 73 | (define-obsolete-function-alias 'ts-node-p 'tsc-node-p "2020-10-13") 74 | (define-obsolete-function-alias 'ts-node-position-range 'tsc-node-position-range "2020-10-13") 75 | (define-obsolete-function-alias 'ts-node-range 'tsc-node-range "2020-10-13") 76 | (define-obsolete-function-alias 'ts-node-start-byte 'tsc-node-start-byte "2020-10-13") 77 | (define-obsolete-function-alias 'ts-node-start-point 'tsc-node-start-point "2020-10-13") 78 | (define-obsolete-function-alias 'ts-node-start-position 'tsc-node-start-position "2020-10-13") 79 | (define-obsolete-function-alias 'ts-node-text 'tsc-node-text "2020-10-13") 80 | (define-obsolete-function-alias 'ts-node-to-sexp 'tsc-node-to-sexp "2020-10-13") 81 | (define-obsolete-function-alias 'ts-node-type 'tsc-node-type "2020-10-13") 82 | (define-obsolete-function-alias 'ts-node-type-id 'tsc-node-type-id "2020-10-13") 83 | (define-obsolete-function-alias 'ts-parse-chunks 'tsc-parse-chunks "2020-10-13") 84 | (define-obsolete-function-alias 'ts-parse-string 'tsc-parse-string "2020-10-13") 85 | (define-obsolete-function-alias 'ts-parser-language 'tsc-parser-language "2020-10-13") 86 | (define-obsolete-function-alias 'ts-parser-p 'tsc-parser-p "2020-10-13") 87 | (define-obsolete-function-alias 'ts-point-from-position 'tsc-point-from-position "2020-10-13") 88 | (define-obsolete-function-alias 'ts-point-p 'tsc-point-p "2020-10-13") 89 | (define-obsolete-function-alias 'ts-point-to-position 'tsc-point-to-position "2020-10-13") 90 | (define-obsolete-function-alias 'ts-pp-to-string 'tsc-pp-to-string "2020-10-13") 91 | (define-obsolete-function-alias 'ts-query-capture-tags 'tsc-query-capture-tags "2020-10-13") 92 | (define-obsolete-function-alias 'ts-query-captures 'tsc-query-captures "2020-10-13") 93 | (define-obsolete-function-alias 'ts-query-count-patterns 'tsc-query-count-patterns "2020-10-13") 94 | (define-obsolete-function-alias 'ts-query-cursor-p 'tsc-query-cursor-p "2020-10-13") 95 | (define-obsolete-function-alias 'ts-query-matches 'tsc-query-matches "2020-10-13") 96 | (define-obsolete-function-alias 'ts-query-p 'tsc-query-p "2020-10-13") 97 | (define-obsolete-function-alias 'ts-range-p 'tsc-range-p "2020-10-13") 98 | (define-obsolete-function-alias 'ts-reset-cursor 'tsc-reset-cursor "2020-10-13") 99 | (define-obsolete-function-alias 'ts-root-node 'tsc-root-node "2020-10-13") 100 | (define-obsolete-function-alias 'ts-set-included-ranges 'tsc-set-included-ranges "2020-10-13") 101 | (define-obsolete-function-alias 'ts-set-language 'tsc-set-language "2020-10-13") 102 | (define-obsolete-function-alias 'ts-tree-language 'tsc-tree-language "2020-10-13") 103 | (define-obsolete-function-alias 'ts-tree-p 'tsc-tree-p "2020-10-13") 104 | (define-obsolete-function-alias 'ts-tree-to-sexp 'tsc-tree-to-sexp "2020-10-13") 105 | 106 | ;;; Semi-public. 107 | (define-obsolete-function-alias 'ts--lang-symbol 'tsc--lang-symbol "2020-10-13") 108 | (define-obsolete-function-alias 'ts--buffer-input 'tsc--buffer-input "2020-10-13") 109 | (define-obsolete-function-alias 'ts--buffer-substring-no-properties 'tsc--buffer-substring-no-properties "2020-10-13") 110 | (define-obsolete-function-alias 'ts--clone-tree 'tsc--clone-tree "2020-10-13") 111 | (define-obsolete-function-alias 'ts--disable-capture 'tsc--disable-capture "2020-10-13") 112 | (define-obsolete-function-alias 'ts--dyn-version 'tsc--dyn-version "2020-10-13") 113 | (define-obsolete-function-alias 'ts--get-child-by-field-name 'tsc--get-child-by-field-name "2020-10-13") 114 | (define-obsolete-function-alias 'ts--invalid-node-step 'tsc--invalid-node-step "2020-10-13") 115 | (define-obsolete-function-alias 'ts--lang-field-id-for-name 'tsc--lang-field-id-for-name "2020-10-13") 116 | (define-obsolete-function-alias 'ts--lang-load-file 'tsc--lang-load-file "2020-10-13") 117 | (define-obsolete-function-alias 'ts--lang-symbol 'tsc--lang-symbol "2020-10-13") 118 | (define-obsolete-function-alias 'ts--lang-type-id-for-name 'tsc--lang-type-id-for-name "2020-10-13") 119 | (define-obsolete-function-alias 'ts--load-language 'tsc--load-language "2020-10-13") 120 | (define-obsolete-function-alias 'ts--make-query 'tsc--make-query "2020-10-13") 121 | (define-obsolete-function-alias 'ts--node-from-steps 'tsc--node-from-steps "2020-10-13") 122 | (define-obsolete-function-alias 'ts--node-steps 'tsc--node-steps "2020-10-13") 123 | (define-obsolete-function-alias 'ts--node-text 'tsc--node-text "2020-10-13") 124 | (define-obsolete-function-alias 'ts--point-from-position 'tsc--point-from-position "2020-10-13") 125 | (define-obsolete-function-alias 'ts--query-capture-names 'tsc--query-capture-names "2020-10-13") 126 | (define-obsolete-function-alias 'ts--query-cursor-captures 'tsc--query-cursor-captures "2020-10-13") 127 | (define-obsolete-function-alias 'ts--query-cursor-captures-1 'tsc--query-cursor-captures-1 "2020-10-13") 128 | (define-obsolete-function-alias 'ts--query-cursor-matches 'tsc--query-cursor-matches "2020-10-13") 129 | (define-obsolete-function-alias 'ts--query-cursor-set-byte-range 'tsc--query-cursor-set-byte-range "2020-10-13") 130 | (define-obsolete-function-alias 'ts--query-cursor-set-point-range 'tsc--query-cursor-set-point-range "2020-10-13") 131 | (define-obsolete-function-alias 'ts--query-start-byte-for-pattern 'tsc--query-start-byte-for-pattern "2020-10-13") 132 | (define-obsolete-function-alias 'ts--reset-parser 'tsc--reset-parser "2020-10-13") 133 | (define-obsolete-function-alias 'ts--save-context 'tsc--save-context "2020-10-13") 134 | (define-obsolete-function-alias 'ts--set-timeout-micros 'tsc--set-timeout-micros "2020-10-13") 135 | (define-obsolete-function-alias 'ts--stringify-patterns 'tsc--stringify-patterns "2020-10-13") 136 | (define-obsolete-function-alias 'ts--timeout-micros 'tsc--timeout-micros "2020-10-13") 137 | (define-obsolete-function-alias 'ts--try-load-dyn 'tsc--try-load-dyn "2020-10-13") 138 | (define-obsolete-function-alias 'ts--without-restriction 'tsc--without-restriction "2020-10-13") 139 | 140 | (provide 'tsc-obsolete) 141 | ;;; tsc-obsolete.el ends here 142 | -------------------------------------------------------------------------------- /doc/-config-ox-hugo.el: -------------------------------------------------------------------------------- 1 | (define-advice org-hugo--gen-front-matter (:around (f data &rest args) inject-weight) 2 | "Add weight to front matter in a way that preserve org's tree ordering." 3 | (setf (map-elt data 'weight) 4 | (line-number-at-pos nil :absolute)) 5 | (apply f data args)) 6 | -------------------------------------------------------------------------------- /doc/config.toml: -------------------------------------------------------------------------------- 1 | # XXX: Hugo, or its themes/plugins don't work well with relativeURLs 2 | baseURL = "https://emacs-tree-sitter.github.io" 3 | # relativeURLs = true 4 | 5 | languageCode = "en-us" 6 | title = "Emacs Tree-sitter" 7 | 8 | googleAnalytics = "UA-138609797-2" 9 | 10 | theme = [ 11 | "docdock", 12 | # "learn", 13 | # "alabaster", 14 | # "hugo-bare-min-theme", 15 | # "techdoc", 16 | 17 | # "docsy", 18 | # "book", 19 | # "docuapi", 20 | ] 21 | 22 | [markup.highlight] 23 | # style = "github" 24 | noClasses = false 25 | 26 | [params] 27 | themeStyle = "original" 28 | 29 | [outputs] 30 | home = [ "HTML", "RSS", "JSON"] 31 | -------------------------------------------------------------------------------- /doc/layouts/partials/XXX: -------------------------------------------------------------------------------- 1 | Can't symlink whole directories, because Hugo seems to have broken handling of template lookup -------------------------------------------------------------------------------- /doc/layouts/partials/custom-head.html: -------------------------------------------------------------------------------- 1 | docdock/custom-head.html -------------------------------------------------------------------------------- /doc/layouts/partials/docdock/custom-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ template "_internal/google_analytics_async.html" . }} 5 | 6 | 13 | -------------------------------------------------------------------------------- /doc/layouts/partials/docdock/header.html: -------------------------------------------------------------------------------- 1 | logo 2 | -------------------------------------------------------------------------------- /doc/layouts/partials/docdock/menu.html: -------------------------------------------------------------------------------- 1 | tree-of-sections.html -------------------------------------------------------------------------------- /doc/layouts/partials/docdock/next-prev-page.html: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /doc/layouts/partials/docdock/tree-of-menu.html: -------------------------------------------------------------------------------- 1 | {{- $currentPage := . }} 2 | 3 | {{- range .Site.Menus.main.ByWeight -}} 4 | {{- template "menu-item" dict "menuEntry" . "currentPage" $currentPage -}} 5 | {{- end -}} 6 | 7 | {{- define "menu-item" -}} 8 | {{- $currentPage := .currentPage -}} 9 | {{- $showvisitedlinks := .Site.Params.showVisitedLinks -}} 10 | {{- with .menuEntry -}} 11 | {{- $menuEntry := . -}} 12 | {{- $isCurrent := $currentPage.IsMenuCurrent .Menu . -}} 13 | {{- $isAncestor := $currentPage.HasMenuCurrent .Menu . -}} 14 | {{- $children := .Children -}} 15 | {{- $numberOfChildren := (len $children) }} 16 | 17 | {{- if ne $numberOfChildren 0 -}} 18 | {{- if ne .Page nil -}} 19 | {{- with .Page -}} 20 | {{- $alwaysOpen := .Params.alwaysopen -}} 21 |
  • 26 |
    27 | {{safeHTML $menuEntry.Pre}}{{.LinkTitle}}{{safeHTML $menuEntry.Post}} 28 | {{- if or $isAncestor (.Params.alwaysopen) }} 29 | 30 | {{- else -}} 31 | 32 | {{- end}} 33 | {{- if $showvisitedlinks}}{{end}} 34 |
    35 | {{- template "menu-children" dict "menuEntry" $menuEntry "currentPage" $currentPage -}} 36 |
  • 37 | {{- end -}} 38 | {{- else -}} 39 |
  • 40 | 43 | {{- template "menu-children" dict "menuEntry" . "currentPage" $currentPage -}} 44 |
  • 45 | {{- end -}} 46 | {{- else -}} 47 | {{- if ne .Page nil -}} 48 | {{- with .Page -}} 49 |
  • 52 | 58 |
  • 59 | {{- end -}} 60 | {{- else -}} 61 |
  • 62 | 65 |
  • 66 | {{- end -}} 67 | {{- end -}} 68 | {{- end -}} 69 | {{- end -}} 70 | 71 | {{- define "menu-children" -}} 72 | {{- $currentPage := .currentPage -}} 73 |
      74 | {{- range .menuEntry.Children -}} 75 | {{- template "menu-item" dict "menuEntry" . "currentPage" $currentPage -}} 76 | {{- end -}} 77 |
    78 | {{- end -}} 79 | -------------------------------------------------------------------------------- /doc/layouts/partials/docdock/tree-of-sections.html: -------------------------------------------------------------------------------- 1 | {{- $currentNode := . }} 2 | {{- $showvisitedlinks := .Site.Params.showVisitedLinks -}} 3 | 4 | {{- if eq .Site.Params.ordersectionsby "title"}} 5 | {{- range .Site.Sections.ByTitle}} 6 | {{- template "section-tree-nav" dict "sect" . "currentnode" $currentNode "showvisitedlinks" $showvisitedlinks}} 7 | {{- end}} 8 | {{- else}} 9 | {{- if .Site.Home.Sections}} 10 | {{- .Scratch.Set "pages" (.Site.Home.RegularPages | union .Site.Home.Sections) }} 11 | {{- else -}} 12 | {{- .Scratch.Set "pages" .Site.Home.RegularPages }} 13 | {{- end}} 14 | {{- $pages := (.Scratch.Get "pages") }} 15 | {{- range $pages.ByWeight}} 16 | {{- template "section-tree-nav" dict "sect" . "currentnode" $currentNode "showvisitedlinks" $showvisitedlinks}} 17 | {{- end}} 18 | {{- end}} 19 | 20 | 21 | {{- define "section-tree-nav" }} 22 | {{- $showvisitedlinks := .showvisitedlinks }} 23 | {{- $currentNode := .currentnode }} 24 | {{- with .sect}} 25 | {{- if and .IsSection (or (not .Params.hidden) $.showhidden)}} 26 | {{- $numberOfPages := (add (len .Pages) (len .Sections)) }} 27 | {{- safeHTML .Params.head}} 28 | 71 | {{- else}} 72 | {{- if not .Params.Hidden }} 73 | 81 | {{- end}} 82 | {{- end}} 83 | {{- end}} 84 | {{- end}} 85 | -------------------------------------------------------------------------------- /doc/layouts/partials/header.html: -------------------------------------------------------------------------------- 1 | docdock/header.html -------------------------------------------------------------------------------- /doc/layouts/partials/menu.html: -------------------------------------------------------------------------------- 1 | docdock/menu.html -------------------------------------------------------------------------------- /doc/layouts/partials/next-prev-page.html: -------------------------------------------------------------------------------- 1 | docdock/next-prev-page.html -------------------------------------------------------------------------------- /doc/layouts/shortcodes/notice.html: -------------------------------------------------------------------------------- 1 | {{ $_hugo_config := `{ "version": 1 }` }} 2 |

    {{ .Inner }}

    3 | -------------------------------------------------------------------------------- /doc/static/css/xxx.css: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------- */ 2 | 3 | /* body { */ 4 | /* background-color: #fdf6e3; */ 5 | /* } */ 6 | 7 | /* body { */ 8 | /* text-align: justify; */ 9 | /* } */ 10 | 11 | /* -------------------------------------------------------------------------- */ 12 | /* Spacing. */ 13 | /* The general idea is that padding should decrease with nesting. */ 14 | 15 | #body-inner .chroma, 16 | #body-inner ul, 17 | #body-inner .notices, 18 | #body-inner .panel { 19 | margin-top: 1rem; 20 | margin-bottom: 1rem; 21 | } 22 | 23 | #body-inner li p, 24 | #body-inner li ul, 25 | #body-inner li .notices, 26 | #body-inner li .chroma, 27 | #body-inner dd p, 28 | #body-inner dd ul, 29 | #body-inner dd .notices, 30 | #body-inner dd .chroma, 31 | #body-inner .notices .chroma { 32 | margin-top: 0.5rem; 33 | margin-bottom: 0.5rem; 34 | } 35 | 36 | /* -------------------------------------------------------------------------- */ 37 | /* Inline code. */ 38 | 39 | /* /\* TODO: Pink *\/ */ 40 | /* code { */ 41 | /* border: 1px solid #DDD; */ 42 | /* border-radius: 4px; */ 43 | /* /\* background-color: rgba(27,31,35,.05); *\/ */ 44 | /* background-color: #EEE; */ 45 | /* } */ 46 | 47 | /* Fix wrong vertical alignment of inline code (was `bottom`). */ 48 | #body-inner code { 49 | vertical-align: baseline; 50 | } 51 | 52 | /* Italicized inline code. */ 53 | em > code { 54 | background-color: inherit; 55 | border: none; 56 | } 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* Headings. */ 60 | 61 | h1, h2 { 62 | /* font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; */ 63 | /* font-weight: bold; */ 64 | text-transform: none; 65 | } 66 | 67 | h1 { 68 | font-weight: bold; 69 | } 70 | 71 | h2 { 72 | font-size: 2rem; 73 | margin-top: 2rem; 74 | margin-bottom: 1rem; 75 | } 76 | 77 | /* -------------------------------------------------------------------------- */ 78 | /* Make code highlighting work. */ 79 | 80 | /* https://discourse.gohugo.io/t/solved-syntax-highlighting-changed-with-hugo-0-29-from-0-27/8678 */ 81 | /* https://github.com/alecthomas/chroma/issues/41 */ 82 | /* We don't modify syntax.css because we want to treat that entire file as machine-generated. */ 83 | 84 | .chroma code { 85 | color: inherit; 86 | background-color: inherit; 87 | } 88 | 89 | .chroma { 90 | color: #777; 91 | background-color: #EEE; 92 | border: 1px solid #DDD; 93 | box-shadow: 2px 2px 2px #EEE; 94 | } 95 | 96 | /* -------------------------------------------------------------------------- */ 97 | /* Make top bar sticky. */ 98 | /* TODO: Make it sticky only when it's needed to toggle the sidebar. */ 99 | 100 | #body > .padding { 101 | overflow: visible; 102 | } 103 | 104 | #top-bar { 105 | position: sticky; 106 | top: 0; 107 | z-index: 9999; 108 | } 109 | 110 | /* -------------------------------------------------------------------------- */ 111 | /* Hide copy-to-clipboard button, except for code blocks. */ 112 | 113 | code + .copy-to-clipboard { 114 | display: none; 115 | } 116 | 117 | .chroma .copy-to-clipboard { 118 | display: inline; 119 | } 120 | 121 | /* -------------------------------------------------------------------------- */ 122 | /* Smaller navigation sidebars. */ 123 | 124 | #body > .padding { 125 | padding-left: 2rem; 126 | padding-right: 2rem; 127 | } 128 | 129 | #navigation .nav { 130 | width: 2rem; 131 | } 132 | 133 | #navigation .nav > i.fa { 134 | font-size: 28px; 135 | } 136 | 137 | /* -------------------------------------------------------------------------- */ 138 | /* Menu sidebar. */ 139 | 140 | #sidebar ul.topics > li.parent, 141 | #sidebar ul.topics > li.active { 142 | background: inherit; 143 | } 144 | 145 | #sidebar ul li li { 146 | border-left: 1px solid transparent; 147 | } 148 | 149 | /* #sidebar ul.topics > li.parent li { */ 150 | /* background: green; */ 151 | /* } */ 152 | 153 | /* #sidebar ul.topics > li.parent.active li { */ 154 | /* background: inherit; */ 155 | /* } */ 156 | 157 | #sidebar ul > li { 158 | text-indent: 0.15rem; 159 | } 160 | 161 | /* #sidebar ul > li > a { */ 162 | /* width: 100%; */ 163 | /* } */ 164 | 165 | #sidebar ul > li.active > a { 166 | /* background-color: #fdf6e3 !important; */ 167 | background-color: white !important; 168 | color: black !important; 169 | } 170 | 171 | #sidebar li.alwaysopen i.fa { 172 | display: none; 173 | } 174 | 175 | #sidebar ul.topics ul { 176 | padding-bottom: 0; 177 | } 178 | 179 | /* -------------------------------------------------------------------------- */ 180 | /* XXX: Either docdock's styling is bad, or ox-hugo's rendering of shortcodes is broken. */ 181 | 182 | div.notices { 183 | padding-bottom: 1px; 184 | padding-left: 1.1rem; 185 | padding-right: 1.1rem; 186 | } 187 | 188 | div.notices p { 189 | padding: 0; 190 | margin-top: 0.6rem; 191 | margin-bottom: 0.6rem; 192 | } 193 | -------------------------------------------------------------------------------- /doc/static/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-tree-sitter/elisp-tree-sitter/1c455b0953da06c40fcf1f21f1ac0c6e179b46d0/doc/static/images/favicon.png -------------------------------------------------------------------------------- /doc/static/img/emacs-tree-sitter-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-tree-sitter/elisp-tree-sitter/1c455b0953da06c40fcf1f21f1ac0c6e179b46d0/doc/static/img/emacs-tree-sitter-96x96.png -------------------------------------------------------------------------------- /doc/static/img/emacs-tree-sitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-tree-sitter/elisp-tree-sitter/1c455b0953da06c40fcf1f21f1ac0c6e179b46d0/doc/static/img/emacs-tree-sitter.png -------------------------------------------------------------------------------- /doc/static/img/emacs-tree-sitter.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-tree-sitter/elisp-tree-sitter/1c455b0953da06c40fcf1f21f1ac0c6e179b46d0/doc/static/img/emacs-tree-sitter.xcf -------------------------------------------------------------------------------- /lisp/tree-sitter-cli.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter-cli.el --- Utilities for tree-sitter CLI -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2020-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; This file contains functions to work with the tree-sitter CLI. It must not 12 | ;; depend (directly on indirectly) on `tsc-dyn'. It shouldn't depend on 13 | ;; `tree-sitter'. 14 | 15 | ;;; Code: 16 | 17 | (eval-when-compile 18 | (require 'subr-x)) 19 | 20 | (defun tree-sitter-cli-directory () 21 | "Return tree-sitter CLI's directory, including the ending separator. 22 | This is the directory where the CLI tool keeps compiled lang definitions, among 23 | other data." 24 | (file-name-as-directory 25 | (expand-file-name 26 | ;; https://github.com/tree-sitter/tree-sitter/blob/1bad6dc/cli/src/config.rs#L20 27 | (if-let ((dir (getenv "TREE_SITTER_DIR"))) 28 | dir 29 | "~/.tree-sitter")))) 30 | 31 | (defun tree-sitter-cli-bin-directory () 32 | "Return the directory used by tree-sitter CLI to store compiled grammars." 33 | (file-name-as-directory 34 | (concat (tree-sitter-cli-directory) "bin"))) 35 | 36 | (provide 'tree-sitter-cli) 37 | ;;; tree-sitter-cli.el ends here 38 | -------------------------------------------------------------------------------- /lisp/tree-sitter-debug.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter-debug.el --- Debug tools for tree-sitter -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2019-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; This file contains debug utilities for tree-sitter. 12 | ;; 13 | ;; (tree-sitter-debug-mode) 14 | 15 | ;;; Code: 16 | 17 | (require 'tree-sitter) 18 | 19 | (require 'generator) 20 | 21 | (defvar-local tree-sitter-debug--tree-buffer nil 22 | "Buffer used to display the syntax tree of this buffer.") 23 | 24 | (defvar-local tree-sitter-debug--source-code-buffer nil 25 | "Source buffer of the syntax tree displayed in this `tree-sitter-debug' buffer.") 26 | 27 | (defgroup tree-sitter-debug nil 28 | "Tree sitter debug and display features." 29 | :group 'tree-sitter) 30 | 31 | (defcustom tree-sitter-debug-jump-buttons nil 32 | "Whether to enable jump-to-node buttons in `tree-sitter-debug' views. 33 | This can have a performance penalty in large buffers." 34 | :type 'boolean 35 | :group 'tree-sitter-debug) 36 | 37 | (defcustom tree-sitter-debug-highlight-jump-region nil 38 | "Whether to highlight the node jumped to. 39 | This only takes effect if `tree-sitter-debug-jump-buttons' is non-nil." 40 | :type 'boolean 41 | :group 'tree-sitter-debug) 42 | 43 | (defun tree-sitter-debug--button-node-lookup (button) 44 | "The function to call when a `tree-sitter-debug' BUTTON is clicked." 45 | (unless tree-sitter-debug--source-code-buffer 46 | (error "No source code buffer set")) 47 | (unless (buffer-live-p tree-sitter-debug--source-code-buffer) 48 | (user-error "Source code buffer has been killed")) 49 | (unless button 50 | (user-error "This function must be called on a button")) 51 | (tree-sitter-debug--goto-node tree-sitter-debug--source-code-buffer 52 | (button-get button 'points-to))) 53 | 54 | (defun tree-sitter-debug--goto-node (buffer byte-range) 55 | "Switch to BUFFER, centering on the region defined by NODE." 56 | (switch-to-buffer-other-window buffer) 57 | (goto-char (byte-to-position (car byte-range))) 58 | (push-mark (byte-to-position (cdr byte-range)) 59 | tree-sitter-debug-highlight-jump-region)) 60 | 61 | (defun tree-sitter-debug--display-node (named-p type start-byte end-byte depth field) 62 | "Display NODE that appears at the given DEPTH in the syntax tree." 63 | (when named-p 64 | (insert (make-string (* 2 depth) ?\ )) 65 | (let* ((field-text (if field 66 | (format " (%s)" field) 67 | "")) 68 | (node-text (format "%s%s:" type field-text))) 69 | (if tree-sitter-debug-jump-buttons 70 | (insert-button node-text 71 | 'action 'tree-sitter-debug--button-node-lookup 72 | 'follow-link t 73 | 'points-to `(,start-byte . ,end-byte)) 74 | (insert node-text)) 75 | (insert "\n")))) 76 | 77 | (defvar tree-sitter-debug-traversal-method :mapc) 78 | 79 | (defun tree-sitter-debug--display-tree (_old-tree) 80 | "Display the current `tree-sitter-tree'." 81 | ;; TODO: Re-render only affected nodes. 82 | (when-let ((tree tree-sitter-tree)) 83 | (with-current-buffer tree-sitter-debug--tree-buffer 84 | (let (buffer-read-only) 85 | (erase-buffer) 86 | (pcase tree-sitter-debug-traversal-method 87 | (:mapc (tsc-traverse-mapc 88 | (lambda (props) 89 | (pcase-let ((`[,named-p ,type ,start-byte ,end-byte ,depth ,field] props)) 90 | (tree-sitter-debug--display-node 91 | named-p type start-byte end-byte depth field))) 92 | tree 93 | [:named-p :type :start-byte :end-byte :depth :field])) 94 | (:iter (iter-do (props (tsc-traverse-iter 95 | tree [:named-p :type :start-byte :end-byte :depth :field])) 96 | (pcase-let ((`[,named-p ,type ,start-byte ,end-byte ,depth ,field] props)) 97 | (tree-sitter-debug--display-node 98 | named-p type start-byte end-byte depth field)))) 99 | (:do (tsc-traverse-do ([named-p type start-byte end-byte depth field] tree) 100 | (tree-sitter-debug--display-node 101 | named-p type start-byte end-byte depth field)))))))) 102 | 103 | (defun tree-sitter-debug--setup () 104 | "Set up syntax tree debugging in the current buffer." 105 | (unless (buffer-live-p tree-sitter-debug--tree-buffer) 106 | (setq tree-sitter-debug--tree-buffer 107 | (get-buffer-create (format "*tree-sitter-tree: %s*" (buffer-name))))) 108 | (let ((source-buffer (current-buffer))) 109 | (with-current-buffer tree-sitter-debug--tree-buffer 110 | (buffer-disable-undo) 111 | (setq tree-sitter-debug--source-code-buffer source-buffer 112 | buffer-read-only t))) 113 | (add-hook 'tree-sitter-after-change-functions #'tree-sitter-debug--display-tree nil :local) 114 | (add-hook 'kill-buffer-hook #'tree-sitter-debug--teardown nil :local) 115 | (display-buffer tree-sitter-debug--tree-buffer) 116 | (tree-sitter-debug--display-tree nil)) 117 | 118 | (defun tree-sitter-debug--teardown () 119 | "Tear down syntax tree debugging in the current buffer." 120 | (remove-hook 'tree-sitter-after-change-functions #'tree-sitter-debug--display-tree :local) 121 | (when (buffer-live-p tree-sitter-debug--tree-buffer) 122 | (kill-buffer tree-sitter-debug--tree-buffer) 123 | (setq tree-sitter-debug--tree-buffer nil))) 124 | 125 | ;;;###autoload 126 | (define-minor-mode tree-sitter-debug-mode 127 | "Toggle syntax tree debugging for the current buffer. 128 | This mode displays the syntax tree in another buffer, and keeps it up-to-date." 129 | :init-value nil 130 | :group 'tree-sitter 131 | (tree-sitter--handle-dependent tree-sitter-debug-mode 132 | #'tree-sitter-debug--setup 133 | #'tree-sitter-debug--teardown)) 134 | 135 | ;;;###autoload 136 | (defun tree-sitter-debug-query (patterns &optional matches tag-assigner) 137 | "Execute query PATTERNS against the current syntax tree and return captures. 138 | 139 | If the optional arg MATCHES is non-nil, matches (from `tsc-query-matches') are 140 | returned instead of captures (from `tsc-query-captures'). 141 | 142 | If the optional arg TAG-ASSIGNER is non-nil, it is passed to `tsc-make-query' to 143 | assign custom tags to capture names. 144 | 145 | This function is primarily useful for debugging purpose. Other packages should 146 | build queries and cursors once, then reuse them." 147 | (let* ((query (tsc-make-query tree-sitter-language patterns tag-assigner)) 148 | (root-node (tsc-root-node tree-sitter-tree))) 149 | (tsc--without-restriction 150 | (if matches 151 | (tsc-query-matches query root-node #'tsc--buffer-substring-no-properties) 152 | (tsc-query-captures query root-node #'tsc--buffer-substring-no-properties))))) 153 | 154 | ;;; TODO: Kill tree-buffer when `tree-sitter' minor mode is turned off. 155 | 156 | (provide 'tree-sitter-debug) 157 | ;;; tree-sitter-debug.el ends here 158 | -------------------------------------------------------------------------------- /lisp/tree-sitter-extras.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter-extras.el --- Extra functionalities of tree-sitter -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2020-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; This file contains extra functionalities built on top of `tree-sitter-mode'. 12 | ;; They are considered experimental, and subjected to frequent changes. 13 | 14 | ;;; Code: 15 | 16 | (require 'tree-sitter) 17 | 18 | (eval-when-compile 19 | (require 'subr-x)) 20 | 21 | (declare-function pixel-posn-y-at-point "ext:pixel-scroll") 22 | (declare-function pixel-scroll-pixel-up "ext:pixel-scroll") 23 | (declare-function pixel-scroll-pixel-down "ext:pixel-scroll") 24 | 25 | (defcustom tree-sitter-save-excursion-try-hard nil 26 | "Whether `tree-sitter-save-excursion' should try as hard as possible." 27 | :type 'boolean 28 | :group 'tree-sitter) 29 | 30 | (defcustom tree-sitter-save-excursion-pixelwise 31 | (not (null (require 'pixel-scroll nil :noerror))) 32 | "Whether `tree-sitter-save-excursion' should restore the location pixelwise." 33 | :type 'boolean 34 | :group 'tree-sitter 35 | :set (lambda (symbol value) 36 | (when (and value (null (require 'pixel-scroll nil :noerror))) 37 | (user-error "Pixelwise location restoration requires `pixel-scroll'; you may need to upgrade Emacs")) 38 | (set-default symbol value))) 39 | 40 | (defun tree-sitter--recenter (screen-line &optional pixel-posn-y) 41 | "Center point on SCREEN-LINE, then optionally scroll to PIXEL-POSN-Y." 42 | (recenter screen-line) 43 | (when pixel-posn-y 44 | (let ((dy (- (pixel-posn-y-at-point) pixel-posn-y))) 45 | (if (> dy 0) 46 | (pixel-scroll-pixel-down dy) 47 | (pixel-scroll-pixel-up (- dy)))))) 48 | 49 | ;;;###autoload 50 | (defmacro tree-sitter-save-excursion (&rest body) 51 | "Save the current location within the syntax tree; execute BODY; restore it. 52 | 53 | If the original location cannot be restored due to the syntax tree changing too 54 | much, this macro behaves like `save-excursion', unless 55 | `tree-sitter-save-excursion-try-hard' is non-nil, in which case it tries to get 56 | as close as possible to the original location. 57 | 58 | After the location is restored, the buffer text is scrolled so that point stays 59 | at roughly the same vertical screen position. If `pixel-scroll' is available and 60 | `tree-sitter-save-excursion-pixelwise' is non-nil, pixelwise scrolling is used 61 | instead, to make this restoration exact." 62 | (declare (indent 0)) 63 | `(let* ((p (point)) 64 | (old-node (tree-sitter-node-at-pos)) 65 | (steps (tsc--node-steps old-node)) 66 | (delta (- p (tsc-node-start-position old-node))) 67 | (screen-line (- (count-screen-lines (window-start) p) 1)) 68 | (pixel-posn-y ,(if tree-sitter-save-excursion-pixelwise 69 | '(pixel-posn-y-at-point) 70 | nil))) 71 | (unwind-protect 72 | (save-excursion ,@body) 73 | (condition-case err 74 | (when-let ((node (tsc--node-from-steps tree-sitter-tree steps))) 75 | (goto-char (+ delta (tsc-node-start-position node))) 76 | (tree-sitter--recenter screen-line pixel-posn-y)) 77 | (tsc--invalid-node-step 78 | ,@(when tree-sitter-save-excursion-try-hard 79 | '((goto-char (tsc-node-start-position (cadr err))) 80 | (tree-sitter--recenter screen-line pixel-posn-y)))))))) 81 | 82 | (provide 'tree-sitter-extras) 83 | ;;; tree-sitter-extras.el ends here 84 | -------------------------------------------------------------------------------- /lisp/tree-sitter-load.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter-load.el --- Language loading for tree-sitter -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2020-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; This file implements functions to search, load and register `tree-sitter' 12 | ;; language objects (grammars). 13 | 14 | ;;; Code: 15 | 16 | (require 'map) 17 | (require 'seq) 18 | 19 | (require 'tsc) 20 | (require 'tree-sitter-cli) 21 | 22 | (eval-when-compile 23 | (require 'pcase)) 24 | 25 | (defvar tree-sitter-languages nil 26 | "An alist of mappings from language name symbols to language objects. 27 | See `tree-sitter-require'.") 28 | 29 | (defvar tree-sitter-load-path (list (tree-sitter-cli-bin-directory)) 30 | "List of directories to search for shared libraries that define languages.") 31 | 32 | (defvar tree-sitter-load-suffixes 33 | (pcase system-type 34 | ;; The CLI tool outputs `.so', but `.dylib' is more sensible on macOS. 35 | ('darwin (list ".dylib" ".so")) 36 | ('berkeley-unix (list ".so")) 37 | ('gnu/linux (list ".so")) 38 | ('android (list ".so")) 39 | ('windows-nt (list ".dll")) 40 | (_ (error "Unsupported system-type %s" system-type))) 41 | "List of suffixes for shared libraries that define tree-sitter languages.") 42 | 43 | ;;; TODO: Allow specifying absolute path. 44 | (defun tree-sitter-load (lang-symbol &optional file native-symbol-name) 45 | "Load a language grammar from FILE and register it under the name LANG-SYMBOL. 46 | If another language was already registered under the same name, override it. 47 | 48 | This function returns the loaded language object. 49 | 50 | FILE should be the base name (without extension) of the native shared library 51 | that exports the language as the native symbol NATIVE-SYMBOL-NAME. 52 | 53 | If FILE is nil, the base name is assumed to be LANG-SYMBOL's name. 54 | 55 | If NATIVE-SYMBOL-NAME is nil, the name of the exported native symbol is assumed 56 | to be LANG-SYMBOL's name, prefixed with \"tree_sitter_\"." 57 | (let* ((lang-name (symbol-name lang-symbol)) 58 | ;; Example: c-sharp -> c_sharp. 59 | (fallback-name (replace-regexp-in-string "-" "_" lang-name)) 60 | (native-symbol-name (or native-symbol-name 61 | (format "tree_sitter_%s" 62 | fallback-name))) 63 | ;; List of base file names to search for. 64 | (files (if file 65 | ;; Use only FILE, if it's given. 66 | (list file) 67 | ;; Otherwise use LANG-SYMBOL. First, as-is. Then, with hyphens 68 | ;; replaced by underscores. 69 | (cons lang-name 70 | (unless (string= lang-name fallback-name) 71 | (list fallback-name))))) 72 | (full-path (seq-some (lambda (base-name) 73 | (locate-file base-name 74 | tree-sitter-load-path 75 | tree-sitter-load-suffixes)) 76 | files))) 77 | (unless full-path 78 | ;; TODO: Define custom error class. 79 | (error "Cannot find shared library for language: %S" lang-symbol)) 80 | (let ((language (tsc--load-language full-path native-symbol-name lang-symbol))) 81 | (setf (map-elt tree-sitter-languages lang-symbol) language) 82 | language))) 83 | 84 | ;;;###autoload 85 | (defun tree-sitter-require (lang-symbol &optional file native-symbol-name) 86 | "Return the language object loaded and registered under the name LANG-SYMBOL. 87 | If the language has not been loaded yet, load it with `tree-sitter-load'. 88 | 89 | FILE should be the base name (without extension) of the native shared library 90 | that exports the language as the native symbol NATIVE-SYMBOL-NAME. 91 | 92 | If FILE is nil, the base name is assumed to be LANG-SYMBOL's name. 93 | 94 | If NATIVE-SYMBOL-NAME is nil, the name of the exported native symbol is assumed 95 | to be LANG-SYMBOL's name, prefixed with \"tree_sitter_\"." 96 | (or (alist-get lang-symbol tree-sitter-languages) 97 | (tree-sitter-load lang-symbol file native-symbol-name))) 98 | 99 | (provide 'tree-sitter-load) 100 | ;;; tree-sitter-load.el ends here 101 | -------------------------------------------------------------------------------- /lisp/tree-sitter-query.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter-query.el --- Tools for running queries live -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2020-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Jorge Javier Araya Navarro 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; This file contains other debug utilities for building queries and see results 12 | ;; in a target buffer. 13 | 14 | ;;; Code: 15 | 16 | (require 'scheme) 17 | (require 'tree-sitter) 18 | 19 | (defgroup tree-sitter-query nil 20 | "Tree-sitter playground." 21 | :group 'tree-sitter) 22 | 23 | (define-derived-mode tree-sitter-query-mode prog-mode "tsc-query-builder" 24 | "Major mode for building tree-sitter queries and testing them live." 25 | :syntax-table scheme-mode-syntax-table 26 | :abbrev-table scheme-mode-abbrev-table) 27 | 28 | (defconst tree-sitter-query-builder-buffer-name "*tree-sitter-query-builder*" 29 | "Name of the builder buffer.") 30 | 31 | (defvar tree-sitter-query--target-buffer nil 32 | "Target buffer to run the queries against.") 33 | 34 | (defface tree-sitter-query-match 35 | '((t :foreground "#000" 36 | :background "#00bfff" 37 | :weight bold)) 38 | "Face for highlight captures in matches." 39 | :group 'tree-sitter-query) 40 | 41 | (defun tree-sitter--echo (&rest args) 42 | "Display a transient message, without logging it in the `*Messages*' buffer." 43 | (let (message-log-max) 44 | (apply #'message args))) 45 | 46 | (defun tree-sitter-query--highlight-capture (capture) 47 | "Highlight CAPTURE in the current buffer." 48 | (pcase-let* ((`(,capture-symbol . ,captured-node) capture) 49 | (`(,node-start . ,node-end) (tsc-node-position-range captured-node)) 50 | (overlay (make-overlay node-start node-end)) 51 | (capture-name (symbol-name capture-symbol))) 52 | ;; Ensure the overlay is deleted when it becomes empty. 53 | (overlay-put overlay 'evaporate t) 54 | (overlay-put overlay 'face 'tree-sitter-query-match) 55 | ;; Use the capture's name as the mouseover tooltip. 56 | (unless (string= capture-name "") 57 | (overlay-put overlay 'help-echo capture-name)))) 58 | 59 | (defun tree-sitter-query--eval-query (patterns) 60 | "Evaluate query PATTERNS against the target buffer." 61 | (with-current-buffer tree-sitter-query--target-buffer 62 | (tsc--without-restriction 63 | (remove-overlays) 64 | (when-let* 65 | ((query 66 | (condition-case err 67 | (tsc-make-query tree-sitter-language patterns) 68 | ((tsc-query-invalid-node-type 69 | tsc-query-invalid-field 70 | tsc-query-invalid-capture) 71 | (tree-sitter--echo "%s: %s" (get (car err) 'error-message) (cadr err)) 72 | nil) 73 | (tsc-query-invalid 74 | (tree-sitter--echo "%s" (get (car err) 'error-message)) 75 | nil))) 76 | (root-node (tsc-root-node tree-sitter-tree)) 77 | (captures (tsc-query-captures query root-node #'tsc--buffer-substring-no-properties))) 78 | (if (= (length captures) 0) 79 | (tree-sitter--echo "No captures found") 80 | (mapc #'tree-sitter-query--highlight-capture captures)))))) 81 | 82 | (defun tree-sitter-query--after-change (&rest _args) 83 | "Run query patterns against the target buffer and update highlighted texts." 84 | (with-current-buffer (get-buffer tree-sitter-query-builder-buffer-name) 85 | (let ((patterns (buffer-string))) 86 | (with-demoted-errors "Error: %S" 87 | (tree-sitter-query--eval-query patterns))))) 88 | 89 | (defun tree-sitter-query--clean-target-buffer () 90 | "Remove all overlays from the target buffer." 91 | (with-current-buffer tree-sitter-query--target-buffer 92 | (remove-overlays)) 93 | (setq tree-sitter-query--target-buffer nil)) 94 | 95 | ;;;###autoload 96 | (defun tree-sitter-query-builder () 97 | "Provide means for developers to write and test tree-sitter queries. 98 | 99 | The buffer on focus when the command is called is set as the target buffer." 100 | (interactive) 101 | (let* ((target-buffer (current-buffer)) 102 | (builder-buffer (get-buffer-create tree-sitter-query-builder-buffer-name)) 103 | (builder-window-is-visible (get-buffer-window builder-buffer))) 104 | (when (eq target-buffer builder-buffer) 105 | (user-error "This buffer cannot be use as target buffer")) 106 | (with-current-buffer target-buffer 107 | (unless tree-sitter-mode 108 | (tree-sitter-mode)) 109 | ;; TODO: The query should be run against the changed range only. 110 | (add-hook 'tree-sitter-after-change-functions #'tree-sitter-query--after-change nil :local) 111 | (setq tree-sitter-query--target-buffer target-buffer)) 112 | (unless builder-window-is-visible 113 | (unless (display-buffer-in-side-window 114 | builder-buffer 115 | '((side . bottom) 116 | (window-height . 10))) 117 | (user-error "Not enough space available for query builder window"))) 118 | (with-current-buffer builder-buffer 119 | (erase-buffer) 120 | (tree-sitter-query-mode) 121 | (add-hook 'after-change-functions #'tree-sitter-query--after-change nil :local) 122 | (add-hook 'kill-buffer-hook #'tree-sitter-query--clean-target-buffer nil :local)) 123 | (setf tree-sitter-query--target-buffer target-buffer) 124 | ;; Switch focus to the query builder window. 125 | (select-window (get-buffer-window builder-buffer)))) 126 | 127 | (provide 'tree-sitter-query) 128 | ;;; tree-sitter-query.el ends here 129 | -------------------------------------------------------------------------------- /lisp/tree-sitter.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter.el --- Incremental parsing system -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2021-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; Keywords: languages tools parsers tree-sitter 8 | ;; Homepage: https://github.com/emacs-tree-sitter/elisp-tree-sitter 9 | ;; Version: 0.18.0 10 | ;; Package-Requires: ((emacs "25.1") (tsc "0.18.0")) 11 | ;; SPDX-License-Identifier: MIT 12 | 13 | ;;; Commentary: 14 | 15 | ;; This is the base framework of the Emacs binding for Tree-sitter, an 16 | ;; incremental parsing system. It includes a minor mode that provides a 17 | ;; buffer-local syntax tree that is updated on every text change. This minor 18 | ;; mode is the base for other libraries to build on. An example is the included 19 | ;; code-highlighting minor mode. 20 | 21 | ;;; Code: 22 | 23 | (require 'tsc) 24 | (require 'tree-sitter-load) 25 | 26 | (defgroup tree-sitter nil 27 | "Incremental parsing system." 28 | :group 'languages) 29 | 30 | (defcustom tree-sitter-after-change-functions nil 31 | "Functions to call each time `tree-sitter-tree' is updated. 32 | Each function will be called with a single argument: the OLD-TREE. This argument 33 | will be nil when the buffer is parsed for the first time. 34 | 35 | For initialization logic that should be run only once, use 36 | `tree-sitter-after-first-parse-hook' instead." 37 | :type 'hook 38 | :group 'tree-sitter) 39 | 40 | (defcustom tree-sitter-after-first-parse-hook nil 41 | "Functions to call after the buffer is parsed for the first time. 42 | This hook should be used for initialization logic that requires inspecting the 43 | syntax tree. It is run after `tree-sitter-mode-hook'." 44 | :type 'hook 45 | :group 'tree-sitter) 46 | 47 | (defcustom tree-sitter-after-on-hook nil 48 | "Functions to call after enabling `tree-sitter-mode'. 49 | Use this to enable other minor modes that depends on the syntax tree." 50 | :type 'hook 51 | :group 'tree-sitter) 52 | 53 | (defcustom tree-sitter-major-mode-language-alist nil 54 | "Alist that maps major modes to tree-sitter language names." 55 | :group 'tree-sitter 56 | :type '(alist :key-type symbol 57 | :value-type symbol)) 58 | 59 | (defvar-local tree-sitter-tree nil 60 | "Tree-sitter syntax tree.") 61 | 62 | (defvar-local tree-sitter-parser nil 63 | "Tree-sitter parser.") 64 | 65 | (defvar-local tree-sitter-language nil 66 | "Tree-sitter language.") 67 | 68 | (defvar-local tree-sitter--text-before-change nil) 69 | 70 | (defvar-local tree-sitter--beg-before-change nil) 71 | 72 | (defun tree-sitter--before-change (beg old-end) 73 | "Update relevant editing states. Installed on `before-change-functions'. 74 | BEG and OLD-END are the begin and end positions of the text to be changed." 75 | (setq tree-sitter--beg-before-change beg) 76 | (tsc--without-restriction 77 | ;; TODO: Fallback to a full parse if this region is too big. 78 | (setq tree-sitter--text-before-change 79 | (when (> old-end beg) 80 | (buffer-substring-no-properties beg old-end))))) 81 | 82 | ;;; TODO: How do we batch *after* hooks to re-parse only once? Maybe using 83 | ;;; `run-with-idle-timer' with 0-second timeout? 84 | ;;; 85 | ;;; XXX: Figure out how to detect whether it was a text-property-only change. 86 | ;;; There's no point in reparsing in these situations. 87 | (defun tree-sitter--after-change (beg new-end old-len) 88 | "Update relevant editing states and reparse the buffer (incrementally). 89 | Installed on `after-change-functions'. 90 | 91 | BEG is the begin position of the change. 92 | NEW-END is the end position of the changed text. 93 | OLD-LEN is the char length of the old text." 94 | (when tree-sitter-tree 95 | (let ((beg:byte (position-bytes beg)) 96 | (new-end:byte (position-bytes new-end)) 97 | old-end:byte 98 | beg:point old-end:point new-end:point) 99 | (tsc--save-context 100 | (setq beg:point (tsc--point-from-position beg) 101 | new-end:point (tsc--point-from-position new-end))) 102 | ;; Compute the old text's end byte position, line number, byte column. 103 | ;; 104 | ;; Tree-sitter works with byte positions, line numbers, byte columns. 105 | ;; Emacs primarily works with character positions. Converting the latter 106 | ;; to the former, for the end of the old text, requires looking at the 107 | ;; actual old text's content. Tree-sitter itself cannot do that, because 108 | ;; it is designed to keep track of only the numbers, not a mirror of the 109 | ;; buffer's text. Without re-designing Emacs's change tracking mechanism, 110 | ;; we store the old text through `tree-sitter--before-change', and inspect 111 | ;; it here. TODO XXX FIX: Improve change tracking in Emacs. 112 | (if (= old-len 0) 113 | (setq old-end:byte beg:byte 114 | old-end:point beg:point) 115 | (let ((old-text tree-sitter--text-before-change) 116 | (rel-beg (1+ (- beg tree-sitter--beg-before-change)))) 117 | ;; FIX: Don't assume before-change's beg and after-change's beg are 118 | ;; the same. 119 | (with-temp-buffer 120 | (insert old-text) 121 | (pcase-let* 122 | ((rel-old-end (+ rel-beg old-len)) 123 | (old-len:byte (- (position-bytes rel-old-end) 124 | (position-bytes rel-beg))) 125 | (`(,beg:linum . ,beg:bytecol) beg:point) 126 | (rel-beg:linum (line-number-at-pos rel-beg)) 127 | (`(,rel-old-end:linum . ,rel-old-end:bytecol) 128 | (tsc--point-from-position rel-old-end)) 129 | (old-diff:linum (- rel-old-end:linum rel-beg:linum)) 130 | (old-end:linum (+ beg:linum old-diff:linum)) 131 | (old-end:bytecol (if (> old-diff:linum 0) 132 | rel-old-end:bytecol 133 | (+ beg:bytecol old-len:byte)))) 134 | (setq old-end:byte (+ beg:byte old-len:byte) 135 | old-end:point `(,old-end:linum . ,old-end:bytecol)))))) 136 | (tsc-edit-tree tree-sitter-tree 137 | beg:byte old-end:byte new-end:byte 138 | beg:point old-end:point new-end:point) 139 | (tree-sitter--do-parse)))) 140 | 141 | (defun tree-sitter--do-parse () 142 | "Parse the current buffer and update the syntax tree." 143 | (let ((old-tree tree-sitter-tree)) 144 | (setq tree-sitter-tree 145 | ;; https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/3 146 | (tsc--without-restriction 147 | (tsc-parse-chunks tree-sitter-parser #'tsc--buffer-input old-tree))) 148 | (run-hook-with-args 'tree-sitter-after-change-functions old-tree))) 149 | 150 | (defun tree-sitter--setup () 151 | "Enable `tree-sitter' in the current buffer." 152 | (unless tree-sitter-language 153 | ;; Determine the language symbol based on `major-mode' . 154 | (let ((lang-symbol (alist-get major-mode tree-sitter-major-mode-language-alist))) 155 | (unless lang-symbol 156 | (error "No language registered for major mode `%s'" major-mode)) 157 | (setq tree-sitter-language (tree-sitter-require lang-symbol)))) 158 | (unless tree-sitter-parser 159 | (setq tree-sitter-parser (tsc-make-parser)) 160 | (tsc-set-language tree-sitter-parser tree-sitter-language)) 161 | (add-hook 'before-change-functions #'tree-sitter--before-change :append :local) 162 | (add-hook 'after-change-functions #'tree-sitter--after-change :append :local)) 163 | 164 | (defun tree-sitter--teardown () 165 | "Disable `tree-sitter' in the current buffer." 166 | (remove-hook 'after-change-functions #'tree-sitter--after-change :local) 167 | (remove-hook 'before-change-functions #'tree-sitter--before-change :local) 168 | (setq tree-sitter-tree nil 169 | tree-sitter-parser nil 170 | tree-sitter-language nil)) 171 | 172 | (defmacro tree-sitter--error-protect (body-form &rest error-forms) 173 | "Execute BODY-FORM with ERROR-FORMS as cleanup code that is executed on error. 174 | Unlike `unwind-protect', ERROR-FORMS is not executed if BODY-FORM does not 175 | signal an error." 176 | (declare (indent 1)) 177 | `(let ((err t)) 178 | (unwind-protect 179 | (prog1 ,body-form 180 | (setq err nil)) 181 | (when err 182 | ,@error-forms)))) 183 | 184 | ;;;###autoload 185 | (define-minor-mode tree-sitter-mode 186 | "Minor mode that keeps an up-to-date syntax tree using incremental parsing." 187 | :init-value nil 188 | :lighter " tree-sitter" 189 | :after-hook (when tree-sitter-mode 190 | (unless tree-sitter-tree 191 | (tree-sitter--do-parse) 192 | (run-hooks 'tree-sitter-after-first-parse-hook))) 193 | (if tree-sitter-mode 194 | (tree-sitter--error-protect 195 | (progn 196 | (tree-sitter--setup) 197 | ;; TODO: When the dependent mode requested us, but then failed to 198 | ;; turn itself on, we should probably turn ourselves off as well. 199 | (with-demoted-errors "tree-sitter-after-on-hook: %S" 200 | (run-hooks 'tree-sitter-after-on-hook))) 201 | (setq tree-sitter-mode nil) 202 | (tree-sitter--teardown)) 203 | (run-hooks 'tree-sitter--before-off-hook) 204 | (tree-sitter--teardown))) 205 | 206 | ;;;###autoload 207 | (defun turn-on-tree-sitter-mode () 208 | "Turn on `tree-sitter-mode' in a buffer, if possible." 209 | ;; FIX: Ignore only known errors. Log the rest, at least. 210 | (ignore-errors 211 | (tree-sitter-mode 1))) 212 | 213 | ;;;###autoload 214 | (define-globalized-minor-mode global-tree-sitter-mode 215 | tree-sitter-mode turn-on-tree-sitter-mode 216 | :init-value nil 217 | :group 'tree-sitter) 218 | 219 | (defun tree-sitter--funcall-form (func) 220 | "Return an equivalent to (funcall FUNC) that can be used in a macro. 221 | If FUNC is a quoted symbol, skip the `funcall' indirection." 222 | (if (and (consp func) 223 | (memq (car func) '(quote function)) 224 | (symbolp (cadr func))) 225 | `(,(cadr func)) 226 | `(funcall ,func))) 227 | 228 | (defmacro tree-sitter--handle-dependent (mode setup-function teardown-function) 229 | "Build the block of code that handles enabling/disabling of a dependent mode. 230 | Use this as the body of the `define-minor-mode' block that defines MODE. 231 | 232 | When MODE is enabled, it automatically enables `tree-sitter-mode'. When MODE is 233 | disabled, it does not disable `tree-sitter-mode', since the latter may have been 234 | requested by end user, or other dependent modes. 235 | 236 | When `tree-sitter-mode' is disabled, it automatically disables MODE, which will 237 | not function correctly otherwise. This happens before `tree-sitter-mode' cleans 238 | up its own state. 239 | 240 | SETUP-FUNCTION is called when MODE is enabled, after MODE variable has been set 241 | to t, and after `tree-sitter-mode' has already been enabled. However, it must 242 | not assume that `tree-sitter-tree' is non-nil, since the first parse may not 243 | happen yet. It should instead set up hooks to handle parse events. 244 | 245 | TEARDOWN-FUNCTION is called when MODE is disabled, after MODE variable has been 246 | set to nil. It should clean up any state set up by MODE, and should not signal 247 | any error. It is also called when SETUP-FUNCTION signals an error, to undo any 248 | partial setup. 249 | 250 | Both SETUP-FUNCTION and TEARDOWN-FUNCTION should be idempotent." 251 | (declare (indent 1)) 252 | (let ((setup (tree-sitter--funcall-form setup-function)) 253 | (teardown (tree-sitter--funcall-form teardown-function))) 254 | `(if ,mode 255 | (progn 256 | (tree-sitter--error-protect 257 | ;; Make sure `tree-sitter-mode' is enabled before MODE. 258 | (progn 259 | (unless tree-sitter-mode 260 | (tree-sitter-mode)) 261 | ,setup) 262 | ;; Setup failed. Clean things up, leave no trace. 263 | (setq ,mode nil) 264 | ,teardown) 265 | ;; Disable MODE when `tree-sitter-mode' is disabled. Quoting is 266 | ;; important, because we don't want a variable-capturing closure. 267 | (add-hook 'tree-sitter--before-off-hook 268 | (with-no-warnings '(lambda () (,mode -1))) 269 | nil :local)) 270 | ,teardown))) 271 | 272 | ;;;###autoload 273 | (define-obsolete-function-alias 'tree-sitter-node-at-point 'tree-sitter-node-at-pos "2021-08-30") 274 | 275 | (define-error 'tree-sitter-invalid-node-type "No such node-type") 276 | 277 | ;;;###autoload 278 | (defun tree-sitter-node-at-pos (&optional node-type pos ignore-invalid-type) 279 | "Return the smallest syntax node of type NODE-TYPE at POS. 280 | NODE-TYPE may be a symbol, corresponding to a named syntax node; a string, 281 | corresponding to an anonymous node, or a keyword, holding a special value. For 282 | the special value `:named', return the smallest named node at POS. For the 283 | special value `:anonymous', return the smallest anonymous node at POS. IF POS is 284 | nil, POS defaults to the point. Unless IGNORE-INVALID-TYPE is non-nil, signal an 285 | error when a specified named NODE-TYPE does not exist in the current grammar. 286 | Whenever NODE-TYPE is non-nil (other than `:named'), it is possible for the 287 | function to return nil." 288 | (when (and (not ignore-invalid-type) 289 | node-type 290 | (not (keywordp node-type))) 291 | (when (= 0 (tsc-lang-node-type-id tree-sitter-language node-type)) 292 | (signal 'tree-sitter-invalid-node-type (list node-type)))) 293 | (let* ((root (tsc-root-node tree-sitter-tree)) 294 | (p (or pos (point))) 295 | (node (if (eq node-type :named) 296 | (tsc-get-named-descendant-for-position-range root p p) 297 | (tsc-get-descendant-for-position-range root p p)))) 298 | (pcase node-type 299 | ('nil node) 300 | (:named node) 301 | (:anonymous (unless (tsc-node-named-p node) node)) 302 | (_ (let ((this node) result) 303 | (while this 304 | (if (equal node-type (tsc-node-type this)) 305 | (setq result this 306 | this nil) 307 | (setq this (tsc-get-parent this)))) 308 | result))))) 309 | 310 | (provide 'tree-sitter) 311 | ;;; tree-sitter.el ends here 312 | -------------------------------------------------------------------------------- /tests/.gitattributes: -------------------------------------------------------------------------------- 1 | data/* -linguist-detectable 2 | -------------------------------------------------------------------------------- /tests/data/change-case-region.rs: -------------------------------------------------------------------------------- 1 | // Change case of "this text" repeatedly. 2 | unsafe { 3 | input_function.call_unprotected((bytepos, point.line_number(), point.byte_column())) 4 | .and_then(|v| v.into_rust()) 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/delete-non-ascii-text.rs: -------------------------------------------------------------------------------- 1 | // Delete the 8 chars next line. 2 | // ấấấấấấấấ 3 | unsafe { 4 | input_function.call_unprotected((bytepos, point.line_number(), point.byte_column())) 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/extend-region.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_pred {} 2 | 3 | // In evil's normal-mode, with point after `abc`, or `;`, or at the end of the file, eval 4 | // (font-lock-flush). A correct implementation would highlight both `abc` and `!`. An incorrect 5 | // implementation would highlight neither. Note that this will change once tree-sitter's error cost 6 | // calculation is improved. See https://github.com/tree-sitter/tree-sitter-rust/issues/82. 7 | abc 8 | impl_pred!(foo, bar); 9 | -------------------------------------------------------------------------------- /tests/data/hl-region-vs-query-region.js: -------------------------------------------------------------------------------- 1 | module.exports = grammar({ 2 | // Set `tree-sitter-hl--extend-region-limit' to an appropriately small number, then call 3 | // `tree-sitter-hl--highlight-region' on the region from 1 to the position after 4 | // `compound_assignment_expr', so that the region is not extended. 5 | rules: { 6 | compound_assignment_expr: $ => [], 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /tests/data/hl.py: -------------------------------------------------------------------------------- 1 | def foo(x): 2 | return x 3 | -------------------------------------------------------------------------------- /tests/data/narrowing.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | ls -lah 6 | -------------------------------------------------------------------------------- /tests/data/query.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use emacs::{defun, Env, Error, GlobalRef, IntoLisp, Result, Value, Vector}; 4 | use tree_sitter::{Node, QueryCursor, QueryErrorKind}; 5 | 6 | use crate::{ 7 | types::{BytePos, Point}, 8 | lang::Language, 9 | node::RNode, 10 | error, 11 | }; 12 | 13 | fn vec_to_vector<'e, T: IntoLisp<'e>>(env: &'e Env, vec: Vec) -> Result> { 14 | let vector = env.make_vector(vec.len(), ())?; 15 | for (i, v) in vec.into_iter().enumerate() { 16 | vector.set(i, v)?; 17 | } 18 | Ok(vector) 19 | } 20 | 21 | // ------------------------------------------------------------------------------------------------- 22 | // Query 23 | 24 | struct Query { 25 | pub(crate) raw: tree_sitter::Query, 26 | pub(crate) capture_tags: Vec, 27 | } 28 | 29 | impl_pred!(query_p, &RefCell); 30 | 31 | /// Create a new query from a SOURCE containing one or more S-expression patterns. 32 | /// 33 | /// The query is associated with LANGUAGE, and can only be run on syntax nodes 34 | /// parsed with LANGUAGE. 35 | /// 36 | /// TAG-ASSIGNER is a function that is called to determine how captures are tagged 37 | /// in query results. It should take a capture name defined in SOURCE's patterns 38 | /// (e.g. "function.builtin"), and return a tag value. If the return value is nil, 39 | /// the associated capture name is disabled. 40 | #[defun(user_ptr)] 41 | fn _make_query(language: Language, source: String, tag_assigner: Value) -> Result { 42 | let mut raw = tree_sitter::Query::new(language.into(), &source).or_else(|err| { 43 | let symbol = match err.kind { 44 | QueryErrorKind::Syntax => error::tsc_query_invalid_syntax, 45 | QueryErrorKind::NodeType => error::tsc_query_invalid_node_type, 46 | QueryErrorKind::Field => error::tsc_query_invalid_field, 47 | QueryErrorKind::Capture => error::tsc_query_invalid_capture, 48 | QueryErrorKind::Predicate => error::tsc_query_invalid_predicate, 49 | QueryErrorKind::Structure => error::tsc_query_invalid_structure, 50 | }; 51 | let byte_pos: BytePos = err.offset.into(); 52 | let point: Point = tree_sitter::Point { row: err.row, column: err.column }.into(); 53 | // TODO: Character position? 54 | // TODO: Convert named node types and field names to symbols and keywords? 55 | tag_assigner.env.signal(symbol, (err.message, point, byte_pos)) 56 | })?; 57 | let capture_names = raw.capture_names().to_vec(); 58 | let mut capture_tags = vec![]; 59 | for name in &capture_names { 60 | let value = tag_assigner.call((name, ))?; 61 | if !value.is_not_nil() { 62 | raw.disable_capture(name); 63 | } 64 | capture_tags.push(value.make_global_ref()) 65 | } 66 | Ok(Query { raw, capture_tags }) 67 | } 68 | 69 | macro_rules! defun_query_methods { 70 | ($($(#[$meta:meta])* $($lisp_name:literal)? fn $name:ident $( ( $( $param:ident : $type:ty ),* ) )? -> $rtype:ty $(; $into:ident)? )*) => { 71 | $( 72 | #[defun$((name = $lisp_name))?] 73 | $(#[$meta])* 74 | fn $name(query: &Query, $( $( $param : $type ),* )? ) -> Result<$rtype> { 75 | Ok(query.raw.$name( $( $( $param ),* )? )$(.$into())?) 76 | } 77 | )* 78 | }; 79 | } 80 | 81 | defun_query_methods! { 82 | /// Return the byte position where the NTH pattern starts in QUERY's source. 83 | "-query-start-byte-for-pattern" fn start_byte_for_pattern(nth: usize) -> BytePos; into 84 | 85 | /// Return the number of patterns in QUERY. 86 | "query-count-patterns" fn pattern_count -> usize 87 | } 88 | 89 | /// Return the names of the captures used in QUERY. 90 | #[defun] 91 | fn _query_capture_names(query: Value) -> Result { 92 | let env = query.env; 93 | let query = query.into_ref::()?; 94 | let names = query.raw.capture_names(); 95 | let vec = env.make_vector(names.len(), ())?; 96 | for (i, name) in names.iter().enumerate() { 97 | vec.set(i, name)?; 98 | } 99 | Ok(vec) 100 | } 101 | 102 | /// Return all of QUERY's available capture tags. 103 | /// See `tsc-make-query' for an explanation of capture tagging. 104 | #[defun(mod_in_name = true)] 105 | fn capture_tags<'e>(env: &'e Env, query: &Query) -> Result> { 106 | let symbols = env.make_vector(query.capture_tags.len(), ())?; 107 | for (i, symbol) in query.capture_tags.iter().enumerate() { 108 | symbols.set(i, symbol)?; 109 | } 110 | Ok(symbols) 111 | } 112 | 113 | /// Disable a certain capture within QUERY, by specifying its NAME. 114 | /// 115 | /// This prevents the capture from being returned in matches, and also avoids any 116 | /// resource usage associated with recording the capture. 117 | #[defun] 118 | fn _disable_capture(query: &mut Query, name: String) -> Result<()> { 119 | query.raw.disable_capture(&name); 120 | Ok(()) 121 | } 122 | 123 | // ------------------------------------------------------------------------------------------------- 124 | // QueryCursor 125 | 126 | impl_pred!(query_cursor_p, &RefCell); 127 | 128 | /// Create a new cursor for executing a given query. 129 | /// 130 | /// The cursor stores the state that is needed to iteratively search for matches. 131 | #[defun(user_ptr)] 132 | fn make_query_cursor() -> Result { 133 | Ok(QueryCursor::new()) 134 | } 135 | 136 | fn text_callback<'e>( 137 | text_function: Value<'e>, 138 | error: &'e RefCell>, 139 | ) -> impl FnMut(Node<'e>) -> String + 'e { 140 | move |child| { 141 | let beg: BytePos = child.start_byte().into(); 142 | let end: BytePos = child.end_byte().into(); 143 | text_function.call((beg, end)).and_then(|v| v.into_rust()).unwrap_or_else(|e| { 144 | error.borrow_mut().replace(e); 145 | "".to_owned() 146 | }) 147 | } 148 | } 149 | 150 | #[defun] 151 | fn _query_cursor_matches<'e>( 152 | cursor: &mut QueryCursor, 153 | query: &Query, 154 | node: &RNode, 155 | text_function: Value<'e>, 156 | ) -> Result> { 157 | let raw = &query.raw; 158 | let error = RefCell::new(None); 159 | let matches = cursor.matches( 160 | raw, 161 | node.borrow().clone(), 162 | text_callback(text_function, &error), 163 | ); 164 | let mut vec = vec![]; 165 | let env = text_function.env; 166 | for m in matches { 167 | if let Some(error) = error.borrow_mut().take() { 168 | return Err(error); 169 | } 170 | let captures = env.make_vector(m.captures.len(), ())?; 171 | for (ci, c) in m.captures.iter().enumerate() { 172 | let captured_node = node.map(|_| c.node); 173 | let capture = env.cons( 174 | &query.capture_tags[c.index as usize], 175 | captured_node 176 | )?; 177 | captures.set(ci, capture)?; 178 | } 179 | let _match = env.cons(m.pattern_index, captures)?; 180 | vec.push(_match); 181 | } 182 | vec_to_vector(env, vec) 183 | } 184 | 185 | // TODO: Make _query_cursor_captures accept a `capture_type` instead, e.g. node type, byte range. 186 | #[defun] 187 | fn _query_cursor_captures_1<'e>( 188 | cursor: &mut QueryCursor, 189 | query: Value<'e>, 190 | node: &RNode, 191 | text_function: Value<'e>, 192 | ) -> Result> { 193 | let query = query.into_rust::<&RefCell>()?.borrow(); 194 | let raw = &query.raw; 195 | let error = RefCell::new(None); 196 | let captures = cursor.captures( 197 | raw, 198 | node.borrow().clone(), 199 | text_callback(text_function, &error), 200 | ); 201 | let mut vec = vec![]; 202 | let env = text_function.env; 203 | for (m, capture_index) in captures { 204 | if let Some(error) = error.borrow_mut().take() { 205 | return Err(error); 206 | } 207 | let c = m.captures[capture_index]; 208 | let beg: BytePos = c.node.start_byte().into(); 209 | let end: BytePos = c.node.end_byte().into(); 210 | let capture = env.cons( 211 | &query.capture_tags[c.index as usize], 212 | env.cons(beg, end)?, 213 | )?; 214 | vec.push((m.pattern_index, capture)); 215 | } 216 | // Prioritize captures from earlier patterns. 217 | vec.sort_unstable_by_key(|(i, _)| *i); 218 | let vector = env.make_vector(vec.len(), ())?; 219 | for (i, (_, v)) in vec.into_iter().enumerate() { 220 | vector.set(i, v)?; 221 | } 222 | Ok(vector) 223 | } 224 | 225 | #[defun] 226 | fn _query_cursor_captures<'e>( 227 | cursor: &mut QueryCursor, 228 | query: Value<'e>, 229 | node: &RNode, 230 | text_function: Value<'e>, 231 | ) -> Result> { 232 | let query = query.into_rust::<&RefCell>()?.borrow(); 233 | let raw = &query.raw; 234 | let error = RefCell::new(None); 235 | let captures = cursor.captures( 236 | raw, 237 | node.borrow().clone(), 238 | text_callback(text_function, &error), 239 | ); 240 | let mut vec = vec![]; 241 | let env = text_function.env; 242 | for (m, capture_index) in captures { 243 | if let Some(error) = error.borrow_mut().take() { 244 | return Err(error); 245 | } 246 | let c = m.captures[capture_index]; 247 | let captured_node = node.map(|_| c.node); 248 | let capture = env.cons( 249 | &query.capture_tags[c.index as usize], 250 | captured_node 251 | )?; 252 | vec.push(capture); 253 | } 254 | 255 | // XXX 256 | let vector = env.make_vector(vec.len(), ())?; 257 | for (i, v) in vec.into_iter().enumerate() { 258 | vector.set(i, v)?; 259 | } 260 | Ok(vector) 261 | } 262 | 263 | /// Limit CURSOR's query executions to the range of byte positions, from BEG to END. 264 | #[defun] 265 | fn _query_cursor_set_byte_range(cursor: &mut QueryCursor, beg: BytePos, end: BytePos) -> Result<()> { 266 | cursor.set_byte_range(beg.into(), end.into()); 267 | Ok(()) 268 | } 269 | 270 | /// Limit CURSOR's query executions to the point range, from BEG to END. 271 | /// 272 | /// A "point" in this context is a (LINE-NUMBER . BYTE-COLUMN) pair. See 273 | /// `tsc-parse-chunks' for a more detailed explanation. 274 | #[defun] 275 | fn _query_cursor_set_point_range(cursor: &mut QueryCursor, beg: Point, end: Point) -> Result<()> { 276 | cursor.set_point_range(beg.into(), end.into()); 277 | Ok(()) 278 | } 279 | -------------------------------------------------------------------------------- /tests/data/range-restriction-and-early-termination.c: -------------------------------------------------------------------------------- 1 | DEFUN ("safe-length", Fsafe_length, Ssafe_length, 1, 1, 0) 2 | -------------------------------------------------------------------------------- /tests/data/types.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | os, 3 | cell::{RefCell, Ref, RefMut}, 4 | ops::{Deref, DerefMut}, 5 | rc::Rc, 6 | mem, 7 | marker::PhantomData, 8 | }; 9 | 10 | use emacs::{defun, Env, Value, Result, IntoLisp, FromLisp, Vector, ErrorKind}; 11 | 12 | use tree_sitter::{Tree, Node, TreeCursor, Parser, Query, QueryCursor}; 13 | 14 | pub fn shared(t: T) -> Shared { 15 | Rc::new(RefCell::new(t)) 16 | } 17 | 18 | unsafe fn erase_lifetime<'t, T>(x: &'t T) -> &'static T { 19 | mem::transmute(x) 20 | } 21 | 22 | macro_rules! impl_newtype_traits { 23 | ($newtype:ty, $inner:ty) => { 24 | impl From<$inner> for $newtype { 25 | #[inline(always)] 26 | fn from(inner: $inner) -> Self { 27 | Self(inner) 28 | } 29 | } 30 | 31 | impl Into<$inner> for $newtype { 32 | #[inline(always)] 33 | fn into(self) -> $inner { 34 | self.0 35 | } 36 | } 37 | }; 38 | ($name:ident) => { 39 | impl_newtype_traits!($name, tree_sitter::$name); 40 | }; 41 | } 42 | 43 | // ------------------------------------------------------------------------------------------------- 44 | // Point 45 | 46 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 47 | pub struct Point(tree_sitter::Point); 48 | 49 | impl_newtype_traits!(Point); 50 | 51 | impl IntoLisp<'_> for Point { 52 | fn into_lisp(self, env: &Env) -> Result { 53 | env.cons(self.line_number(), self.byte_column()) 54 | } 55 | } 56 | 57 | impl FromLisp<'_> for Point { 58 | fn from_lisp(value: Value) -> Result { 59 | let row = value.car::()? - 1; 60 | let column = value.cdr()?; 61 | Ok(tree_sitter::Point { row, column }.into()) 62 | } 63 | } 64 | 65 | impl Point { 66 | #[inline(always)] 67 | pub(crate) fn line_number(&self) -> usize { 68 | self.0.row + 1 69 | } 70 | 71 | #[inline(always)] 72 | pub(crate) fn byte_column(&self) -> usize { 73 | self.0.column 74 | } 75 | } 76 | 77 | // ------------------------------------------------------------------------------------------------- 78 | // Emacs Byte Position (1-based, which is different from byte offset, which is 0-based). 79 | 80 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 81 | pub struct BytePos(usize); 82 | 83 | impl From for BytePos { 84 | #[inline(always)] 85 | fn from(byte_offset: usize) -> Self { 86 | Self(byte_offset + 1) 87 | } 88 | } 89 | 90 | impl Into for BytePos { 91 | #[inline(always)] 92 | fn into(self) -> usize { 93 | self.0 - 1 94 | } 95 | } 96 | 97 | impl FromLisp<'_> for BytePos { 98 | #[inline(always)] 99 | fn from_lisp(value: Value) -> Result { 100 | value.into_rust().map(Self) 101 | } 102 | } 103 | 104 | impl IntoLisp<'_> for BytePos { 105 | #[inline(always)] 106 | fn into_lisp(self, env: &Env) -> Result { 107 | self.0.into_lisp(env) 108 | } 109 | } 110 | 111 | // ------------------------------------------------------------------------------------------------- 112 | // Range 113 | 114 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 115 | pub struct Range(pub(crate) tree_sitter::Range); 116 | 117 | impl_newtype_traits!(Range); 118 | 119 | impl IntoLisp<'_> for Range { 120 | fn into_lisp(self, env: &Env) -> Result { 121 | let inner = self.0; 122 | let start_byte_pos: BytePos = inner.start_byte.into(); 123 | let end_byte_pos: BytePos = inner.end_byte.into(); 124 | env.vector(( 125 | start_byte_pos, 126 | end_byte_pos, 127 | Point(inner.start_point), 128 | Point(inner.end_point), 129 | )) 130 | } 131 | } 132 | 133 | impl FromLisp<'_> for Range { 134 | fn from_lisp(value: Value) -> Result { 135 | let vector: Vector = value.into_rust()?; 136 | let start_byte = vector.get::(0)?.into(); 137 | let end_byte = vector.get::(1)?.into(); 138 | let start_point = vector.get::(2)?.into(); 139 | let end_point = vector.get::(3)?.into(); 140 | Ok(tree_sitter::Range { start_byte, end_byte, start_point, end_point }.into()) 141 | } 142 | } 143 | 144 | // ------------------------------------------------------------------------------------------------- 145 | // Language 146 | 147 | #[derive(Copy, Clone)] 148 | #[repr(transparent)] 149 | pub struct Language(pub(crate) tree_sitter::Language); 150 | 151 | impl_newtype_traits!(Language); 152 | 153 | unsafe extern "C" fn no_op(_: *mut os::raw::c_void) {} 154 | 155 | impl IntoLisp<'_> for Language { 156 | fn into_lisp(self, env: &Env) -> Result { 157 | // Safety: Language has the same representation as the opaque pointer type. 158 | let ptr: *mut os::raw::c_void = unsafe { mem::transmute(self) }; 159 | // Safety: The finalizer does nothing. 160 | unsafe { env.make_user_ptr(Some(no_op::), ptr) } 161 | } 162 | } 163 | 164 | impl FromLisp<'_> for Language { 165 | fn from_lisp(value: Value) -> Result { 166 | match value.get_user_finalizer()? { 167 | Some(fin) if fin == no_op:: => { 168 | let ptr = value.get_user_ptr()?; 169 | // Safety: Language has the same representation as the opaque pointer type. 170 | Ok(unsafe { mem::transmute(ptr) }) 171 | } 172 | _ => Err(ErrorKind::WrongTypeUserPtr { expected: "TreeSitterLanguage" }.into()) 173 | } 174 | } 175 | } 176 | 177 | // ------------------------------------------------------------------------------------------------- 178 | // Tree 179 | 180 | pub type Shared = Rc>; 181 | 182 | // XXX: If we pass a &, #[defun] will assume it's refcell-wrapped. If we pass a Value, we need 183 | // .into_rust() boilerplate. This is a trick to avoid both. 184 | pub type Borrowed<'e, T> = &'e Shared; 185 | 186 | // ------------------------------------------------------------------------------------------------- 187 | // Node 188 | 189 | /// Wrapper around `tree_sitter::Node` that can have 'static lifetime, by keeping a ref-counted 190 | /// reference to the underlying tree. 191 | #[derive(Clone)] 192 | pub struct RNode { 193 | tree: Shared, 194 | inner: Node<'static>, 195 | } 196 | 197 | pub struct RNodeBorrow<'e> { 198 | #[allow(unused)] 199 | reft: Ref<'e, Tree>, 200 | node: &'e Node<'e>, 201 | } 202 | 203 | impl<'e> Deref for RNodeBorrow<'e> { 204 | type Target = Node<'e>; 205 | 206 | #[inline] 207 | fn deref(&self) -> &Self::Target { 208 | self.node 209 | } 210 | } 211 | 212 | pub struct RNodeBorrowMut<'e> { 213 | #[allow(unused)] 214 | reft: RefMut<'e, Tree>, 215 | node: Node<'e>, 216 | } 217 | 218 | impl<'e> Deref for RNodeBorrowMut<'e> { 219 | type Target = Node<'e>; 220 | 221 | #[inline] 222 | fn deref(&self) -> &Self::Target { 223 | &self.node 224 | } 225 | } 226 | 227 | impl<'e> DerefMut for RNodeBorrowMut<'e> { 228 | #[inline] 229 | fn deref_mut(&mut self) -> &mut Self::Target { 230 | &mut self.node 231 | } 232 | } 233 | 234 | impl PartialEq for RNode { 235 | fn eq(&self, other: &Self) -> bool { 236 | self.inner == other.inner 237 | } 238 | } 239 | 240 | impl IntoLisp<'_> for RNode { 241 | fn into_lisp(self, env: &Env) -> Result { 242 | RefCell::new(self).into_lisp(env) 243 | } 244 | } 245 | 246 | impl RNode { 247 | pub fn new<'e, F: FnOnce(&'e Tree) -> Node<'e>>(tree: Shared, f: F) -> Self { 248 | let rtree = unsafe { erase_lifetime(&*tree.borrow()) }; 249 | let inner = unsafe { mem::transmute(f(rtree)) }; 250 | Self { tree, inner } 251 | } 252 | 253 | pub fn clone_tree(&self) -> Shared { 254 | self.tree.clone() 255 | } 256 | 257 | pub fn map<'e, F: FnOnce(&Node<'e>) -> Node<'e>>(&self, f: F) -> Self { 258 | Self::new(self.clone_tree(), |_| f(&self.inner)) 259 | } 260 | 261 | #[inline] 262 | pub fn borrow(&self) -> RNodeBorrow { 263 | let reft = self.tree.borrow(); 264 | let node = &self.inner; 265 | RNodeBorrow { reft, node } 266 | } 267 | 268 | #[inline] 269 | pub fn borrow_mut(&mut self) -> RNodeBorrowMut { 270 | let reft = self.tree.borrow_mut(); 271 | let node = self.inner; 272 | RNodeBorrowMut { reft, node } 273 | } 274 | } 275 | 276 | // ------------------------------------------------------------------------------------------------- 277 | // Cursor 278 | 279 | /// Wrapper around `tree_sitter::TreeCursor` that can have 'static lifetime, by keeping a 280 | /// ref-counted reference to the underlying tree. 281 | pub struct RCursor { 282 | tree: Shared, 283 | inner: TreeCursor<'static>, 284 | } 285 | 286 | pub struct RCursorBorrow<'e> { 287 | #[allow(unused)] 288 | reft: Ref<'e, Tree>, 289 | cursor: &'e TreeCursor<'e>, 290 | } 291 | 292 | impl<'e> Deref for RCursorBorrow<'e> { 293 | type Target = TreeCursor<'e>; 294 | 295 | #[inline] 296 | fn deref(&self) -> &Self::Target { 297 | self.cursor 298 | } 299 | } 300 | 301 | pub struct RCursorBorrowMut<'e> { 302 | #[allow(unused)] 303 | reft: Ref<'e, Tree>, 304 | cursor: &'e mut TreeCursor<'e>, 305 | } 306 | 307 | impl<'e> Deref for RCursorBorrowMut<'e> { 308 | type Target = TreeCursor<'e>; 309 | 310 | #[inline] 311 | fn deref(&self) -> &Self::Target { 312 | self.cursor 313 | } 314 | } 315 | 316 | impl<'e> DerefMut for RCursorBorrowMut<'e> { 317 | #[inline] 318 | fn deref_mut(&mut self) -> &mut Self::Target { 319 | self.cursor 320 | } 321 | } 322 | 323 | impl RCursor { 324 | pub fn new<'e, F: FnOnce(&'e Tree) -> TreeCursor<'e>>(tree: Shared, f: F) -> Self { 325 | let rtree = unsafe { erase_lifetime(&*tree.borrow()) }; 326 | let inner = unsafe { mem::transmute(f(rtree)) }; 327 | Self { tree, inner } 328 | } 329 | 330 | pub fn clone_tree(&self) -> Shared { 331 | self.tree.clone() 332 | } 333 | 334 | #[inline] 335 | pub fn borrow(&self) -> RCursorBorrow { 336 | let reft = self.tree.borrow(); 337 | let cursor = &self.inner; 338 | RCursorBorrow { reft, cursor } 339 | } 340 | 341 | #[inline] 342 | pub fn borrow_mut<'e>(&'e mut self) -> RCursorBorrowMut { 343 | let reft: Ref<'e, Tree> = self.tree.borrow(); 344 | // XXX: Explain the safety here. 345 | let cursor: &'e mut _ = unsafe { mem::transmute(&mut self.inner) }; 346 | RCursorBorrowMut { reft, cursor } 347 | } 348 | } 349 | 350 | // ------------------------------------------------------------------------------------------------- 351 | 352 | pub enum Either<'e, L, R> where L: FromLisp<'e>, R: FromLisp<'e> { 353 | Left(L, PhantomData<&'e ()>), 354 | Right(R, PhantomData<&'e ()>), 355 | } 356 | 357 | impl<'e, L, R> FromLisp<'e> for Either<'e, L, R> where L: FromLisp<'e>, R: FromLisp<'e> { 358 | fn from_lisp(value: Value<'e>) -> Result { 359 | if let Ok(value) = value.into_rust::() { 360 | return Ok(Either::Left(value, PhantomData)); 361 | } 362 | let value = value.into_rust::()?; 363 | Ok(Either::Right(value, PhantomData)) 364 | } 365 | } 366 | 367 | macro_rules! impl_pred { 368 | ($name:ident, $type:ty) => { 369 | #[defun] 370 | fn $name(value: Value) -> Result { 371 | Ok(value.into_rust::<$type>().is_ok()) 372 | } 373 | }; 374 | } 375 | 376 | // TODO: Add docstring for these. 377 | impl_pred!(language_p, Language); 378 | impl_pred!(range_p, Range); 379 | impl_pred!(point_p, Point); 380 | impl_pred!(parser_p, &RefCell); 381 | impl_pred!(tree_p, &Shared); 382 | impl_pred!(node_p, &RefCell); 383 | impl_pred!(cursor_p, &RefCell); 384 | impl_pred!(query_p, &RefCell); 385 | impl_pred!(query_cursor_p, &RefCell); 386 | -------------------------------------------------------------------------------- /tests/tree-sitter-bench.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter-bench.el --- Benchmarks for tree-sitter.el -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2019-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; Benchmarks for `tree-sitter'. 12 | 13 | ;;; Code: 14 | 15 | ;; Local Variables: 16 | ;; no-byte-compile: t 17 | ;; End: 18 | 19 | (require 'tree-sitter-tests-utils) 20 | 21 | (ert-deftest parsing::bench () 22 | (tsc-test-with c parser 23 | (tsc-test-with-file "data/types.rs" 24 | (let ((n 0)) 25 | (while (<= n 4) 26 | (let ((tsc--buffer-input-chunk-size (* 1024 (expt 2 n)))) 27 | (garbage-collect) 28 | (message "tsc-parse-chunks %6d %s" tsc--buffer-input-chunk-size 29 | (benchmark-run 10 30 | (tsc-parse-chunks parser #'tsc--buffer-input nil))) 31 | (cl-incf n))))))) 32 | 33 | (ert-deftest cursor::bench () 34 | (tsc-test-lang-with-file rust "data/types.rs" 35 | (require 'rust-mode) 36 | (rust-mode) 37 | (tree-sitter-mode) 38 | (let ((props [:named-p :type :start-byte :end-byte])) 39 | (dolist (n '(1 10 100)) 40 | (message "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 41 | (garbage-collect) 42 | (message "%10s %3d %s" :do n 43 | (eval `(benchmark-run-compiled ,n 44 | (tsc-traverse-do ([named-p type start-byte end-byte] tree-sitter-tree) 45 | named-p type start-byte end-byte)))) 46 | (garbage-collect) 47 | (message "%10s %3d %s" :mapc n 48 | (eval `(benchmark-run-compiled ,n 49 | (tsc-traverse-mapc 50 | tsc-test-no-op 51 | tree-sitter-tree 52 | ,props)))) 53 | (garbage-collect) 54 | (message "%10s %3d %s" :iter n 55 | (eval `(benchmark-run-compiled ,n 56 | (iter-do (_ (tsc-traverse-iter tree-sitter-tree ,props)) 57 | (tsc-test-no-op))))) 58 | (garbage-collect) 59 | (message "%10s %3d %s" :node-mapc n 60 | (eval `(benchmark-run-compiled ,n 61 | (tsc-traverse-mapc 62 | (lambda (node) 63 | (tsc-node-named-p node) 64 | (tsc-node-type node) 65 | (tsc-node-start-byte node) 66 | (tsc-node-end-byte node) 67 | (tsc-test-no-op)) 68 | tree-sitter-tree)))) 69 | (garbage-collect) 70 | (message "%10s %3d %s" :node-iter n 71 | (eval `(benchmark-run-compiled ,n 72 | (iter-do (node (tsc-traverse-iter tree-sitter-tree)) 73 | (tsc-node-named-p node) 74 | (tsc-node-type node) 75 | (tsc-node-start-byte node) 76 | (tsc-node-end-byte node) 77 | (tsc-test-no-op))))) 78 | (garbage-collect) 79 | (message "%10s %3d %s" 'funcall n 80 | (eval `(benchmark-run-compiled ,(* 3429 n) 81 | (funcall tsc-test-no-op ,props 5)))))))) 82 | 83 | (ert-deftest hl::bench () 84 | (tsc-test-lang-with-file rust "data/types.rs" 85 | (setq tree-sitter-hl-default-patterns (tree-sitter-langs--hl-default-patterns 'rust)) 86 | (require 'rust-mode) 87 | (rust-mode) 88 | (font-lock-mode) 89 | (font-lock-set-defaults) 90 | (dolist (n '(1 10 100)) 91 | (message "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 92 | (tree-sitter-hl-mode) 93 | (garbage-collect) 94 | (message "tree-sitter-hl %2d %s" n (eval `(benchmark-run-compiled ,n (font-lock-ensure)))) 95 | (tree-sitter-hl-mode -1) 96 | (font-lock-ensure) 97 | (garbage-collect) 98 | (message " font-lock %2d %s" n (eval `(benchmark-run-compiled ,n (font-lock-ensure))))))) 99 | 100 | (ert-deftest debug::bench () 101 | (tsc-test-lang-with-file rust "data/types.rs" 102 | (setq tree-sitter-hl-default-patterns (tree-sitter-langs--hl-default-patterns 'rust)) 103 | (require 'rust-mode) 104 | (rust-mode) 105 | (dolist (n '(1 10 100)) 106 | (message "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 107 | (dolist (tree-sitter-debug-traversal-method '(:mapc :iter :do)) 108 | (garbage-collect) 109 | (message "%10s %3d %s" tree-sitter-debug-traversal-method n 110 | (eval `(benchmark-run-compiled ,n 111 | (progn (tree-sitter-debug-mode -1) 112 | (tree-sitter-debug-mode))))))))) 113 | 114 | (provide 'tree-sitter-bench) 115 | ;;; tree-sitter-bench.el ends here 116 | -------------------------------------------------------------------------------- /tests/tree-sitter-tests-utils.el: -------------------------------------------------------------------------------- 1 | ;;; tree-sitter-tests-utils.el --- Utils for tree-sitter-tests.el -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2019-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; Utils for `tree-sitter-tests'. 12 | 13 | ;;; Code: 14 | 15 | (setq tsc-dyn-get-from nil) 16 | (require 'tree-sitter) 17 | (require 'tree-sitter-debug) 18 | 19 | (defvar tree-sitter-langs--testing) 20 | ;;; Disable grammar downloading. 21 | (let ((tree-sitter-langs--testing nil)) ; No need to disable anymore. 22 | (require 'tree-sitter-langs)) 23 | 24 | ;;; Build the grammars, if necessary. 25 | (dolist (lang-symbol '(rust python javascript c)) 26 | (tree-sitter-langs-ensure lang-symbol)) 27 | 28 | ;; XXX: Bash grammar failed 'tree-sitter test' on Windows: 'Escaped newlines'. 29 | (with-demoted-errors "Failed to ensure bash grammar %s" 30 | (tree-sitter-langs-ensure 'bash)) 31 | 32 | (require 'ert) 33 | (require 'generator) 34 | 35 | (eval-when-compile 36 | (require 'subr-x) 37 | (require 'cl-lib)) 38 | 39 | ;;; ---------------------------------------------------------------------------- 40 | ;;; Helpers. 41 | 42 | (defun tsc-test-make-parser (lang-symbol) 43 | "Return a new parser for LANG-SYMBOL." 44 | (let ((parser (tsc-make-parser)) 45 | (language (tree-sitter-require lang-symbol))) 46 | (tsc-set-language parser language) 47 | parser)) 48 | 49 | (defun tsc-test-full-path (relative-path) 50 | "Return full path from project RELATIVE-PATH." 51 | (concat (file-name-directory (locate-library "tree-sitter-tests.el")) 52 | relative-path)) 53 | 54 | (defun tsc-test-tree-sexp (sexp &optional reset) 55 | "Check that the current syntax tree's sexp representation is SEXP. 56 | If RESET is non-nil, also do another full parse and check again." 57 | (should (equal (read (tsc-tree-to-sexp tree-sitter-tree)) sexp)) 58 | (when reset 59 | (setq tree-sitter-tree nil) 60 | (tree-sitter--do-parse) 61 | (tsc-test-tree-sexp sexp))) 62 | 63 | (defun tsc-test-use-lang (lang-symbol) 64 | "Turn on `tree-sitter-mode' in the current buffer, using language LANG-SYMBOL." 65 | (setq tree-sitter-language (tree-sitter-require lang-symbol)) 66 | (ignore-errors 67 | (setq tree-sitter-hl-default-patterns 68 | (tree-sitter-langs--hl-default-patterns lang-symbol))) 69 | (add-hook 'tree-sitter-after-first-parse-hook 70 | (lambda () (should (not (null tree-sitter-tree))))) 71 | (tree-sitter-mode)) 72 | 73 | (defun tsc--listify (x) 74 | (if (listp x) 75 | x 76 | (list x))) 77 | 78 | (defun tsc--hl-at (pos face) 79 | "Return t if text at POS is highlighted with FACE." 80 | (memq face (tsc--listify (get-text-property pos 'face)))) 81 | 82 | ;; In Emacs 28,`font-lock-ensure' checks `font-lock-specified-p' first. 83 | ;; See https://github.com/emacs-tree-sitter/elisp-tree-sitter/pull/220#issuecomment-1120423580. 84 | (defun tsc--hl-ensure (&optional beg end) 85 | (funcall font-lock-ensure-function 86 | (or beg (point-min)) (or end (point-max)))) 87 | 88 | (defun tsc-test-no-op (&rest _args)) 89 | 90 | (defvar tsc-test-no-op 91 | (byte-compile #'tsc-test-no-op)) 92 | 93 | (defun tsc-test-render-node (type named-p start-byte end-byte field depth) 94 | (when named-p 95 | (message "%s%s%S (%s . %s)" (make-string (* 2 depth) ?\ ) 96 | (if field 97 | (format "%s " field) 98 | "") 99 | type start-byte end-byte))) 100 | 101 | (defmacro tsc-test-with (lang-symbol var &rest body) 102 | "Eval BODY with VAR bound to a new parser for LANG-SYMBOL." 103 | (declare (indent 2)) 104 | `(let ((,var (tsc-test-make-parser ',lang-symbol))) 105 | ,@body)) 106 | 107 | (defmacro tsc-test-with-file (relative-path &rest body) 108 | "Eval BODY in a temp buffer filled with content of the file at RELATIVE-PATH." 109 | (declare (indent 1)) 110 | `(with-temp-buffer 111 | (let ((coding-system-for-read 'utf-8)) 112 | (insert-file-contents (tsc-test-full-path ,relative-path))) 113 | ,@body)) 114 | 115 | (defmacro tsc-test-lang-with-file (lang-symbol relative-path &rest body) 116 | "Eval BODY in a temp buffer filled with content of the file at RELATIVE-PATH. 117 | `tree-sitter-mode' is turned on, using the given language LANG-SYMBOL." 118 | (declare (indent 2)) 119 | `(tsc-test-with-file ,relative-path 120 | (tsc-test-use-lang ',lang-symbol) 121 | ,@body)) 122 | 123 | (defmacro tsc-test-with-advice (symbol where function &rest body) 124 | "Eval BODY while advising SYMBOL with FUNCTION at WHERE." 125 | (declare (indent 3)) 126 | `(progn 127 | (advice-add ,symbol ,where ,function) 128 | (unwind-protect 129 | ,@body 130 | (advice-remove ,symbol ,function)))) 131 | 132 | (defmacro tsc-test-capture-messages (&rest body) 133 | `(with-temp-buffer 134 | (let ((buf (current-buffer))) 135 | (tsc-test-with-advice 'message :override 136 | (lambda (fmt &rest args) 137 | (with-current-buffer buf 138 | (insert (apply #'format-message fmt args) "\n"))) 139 | ,@body) 140 | (with-current-buffer buf 141 | (buffer-string))))) 142 | 143 | ;; Local Variables: 144 | ;; no-byte-compile: t 145 | ;; End: 146 | 147 | (provide 'tree-sitter-tests-utils) 148 | ;;; tree-sitter-tests-utils.el ends here 149 | -------------------------------------------------------------------------------- /tests/tsc-dyn-get-tests.el: -------------------------------------------------------------------------------- 1 | ;;; tsc-dyn-get-tests.el --- Tests for tsc-dyn-get.el -*- lexical-binding: t; coding: utf-8 -*- 2 | 3 | ;; Copyright (C) 2021-2025 emacs-tree-sitter maintainers 4 | ;; 5 | ;; Author: Tuấn-Anh Nguyễn 6 | ;; Maintainer: Jen-Chieh Shen 7 | ;; SPDX-License-Identifier: MIT 8 | 9 | ;;; Commentary: 10 | 11 | ;; Tests for the code that downloads/builds and loads `tsc-dyn'. Since Emacs 12 | ;; cannot unload dynamic modules, each test needs to run in a fresh Emacs 13 | ;; process. These are considered expensive "integration tests" that should be 14 | ;; run once in a while, not during normal development. 15 | 16 | (require 'async) 17 | 18 | (eval-when-compile 19 | (require 'subr-x)) 20 | 21 | (defun -remove-tsc-from-load-path () 22 | "Remove directories containing `tsc' so that we can properly test. 23 | This is mainly needed because we use `tree-sitter''s env, which depends on 24 | `tsc'." 25 | (dolist (lib '("tsc" "tsc-dyn")) 26 | (while 27 | (when-let* ((loc (locate-library lib)) 28 | (extra-dir (file-name-directory loc))) 29 | (setq load-path (delete (file-name-as-directory extra-dir) load-path)) 30 | (setq load-path (delete (directory-file-name extra-dir) load-path)))))) 31 | 32 | (defvar *root-dir* 33 | (file-name-directory 34 | (directory-file-name 35 | (file-name-directory 36 | (locate-library "tsc-dyn-get-tests.el"))))) 37 | 38 | (defmacro -with-sub-eval (&rest body) 39 | (declare (indent 0)) 40 | `(async-sandbox 41 | (lambda () 42 | (add-to-list 'load-path (concat ,*root-dir* "tests")) 43 | (load "tsc-dyn-get-tests.el") 44 | (-remove-tsc-from-load-path) 45 | ,@body))) 46 | 47 | (defmacro -sub-funcall (sym &rest args) 48 | (declare (indent 1)) 49 | `(-with-sub-eval 50 | (funcall ',sym ,@args))) 51 | 52 | (defmacro -with-fresh-tsc (&rest body) 53 | `(let ((default-directory (file-name-as-directory 54 | (make-temp-file "tsc-dyn-get-tests." :dir)))) 55 | (message "Fresh copy of `tsc' in %s" default-directory) 56 | (dolist (file '("tsc.el" "tsc-dyn-get.el" "tsc-obsolete.el" 57 | "src" "Cargo.toml" "Cargo.lock" ".cargo")) 58 | (let ((path (concat (file-name-as-directory 59 | (concat *root-dir* "core")) 60 | file))) 61 | (if (file-directory-p path) 62 | (copy-directory path file) 63 | (copy-file path file)))) 64 | (prog1 65 | (eval (append 66 | `(-with-sub-eval 67 | (add-to-list 'load-path ,default-directory)) 68 | ',body)) 69 | (delete-directory default-directory :recursive)))) 70 | 71 | (defmacro -with-call-count (sym &rest body) 72 | (declare (indent 1)) 73 | `(let* ((cnt 0) 74 | (incr (lambda (&rest _args) (setq cnt (1+ cnt))))) 75 | (advice-add ,sym :before incr) 76 | (cons 77 | (unwind-protect 78 | ,@body 79 | (advice-remove ,sym incr)) 80 | cnt))) 81 | 82 | (ert-deftest tsc-dyn-get:no-sources () 83 | (should-error (-with-fresh-tsc 84 | (setq tsc-dyn-get-from nil) 85 | (require 'tsc)) 86 | :type 'file-missing)) 87 | 88 | (ert-deftest tsc-dyn-get:only-github () 89 | (should (equal (-with-fresh-tsc 90 | (setq tsc-dyn-get-from '(:github)) 91 | (-with-call-count 'tsc-dyn-get--github 92 | (require 'tsc))) 93 | '(tsc . 1)))) 94 | 95 | (ert-deftest tsc-dyn-get:only-compilation () 96 | (should (equal (-with-fresh-tsc 97 | (setq tsc-dyn-get-from '(:compilation)) 98 | (-with-call-count 'tsc-dyn-get--build 99 | (require 'tsc))) 100 | '(tsc . 1)))) 101 | 102 | ;;; Code: 103 | ;; Local Variables: 104 | ;; no-byte-compile: t 105 | ;; End: 106 | --------------------------------------------------------------------------------