├── .envrc ├── .github ├── CODEOWNERS ├── workflows │ ├── trunk.yaml │ ├── ci.yaml │ ├── release.yaml │ ├── lint-package.yaml │ ├── test-package.yaml │ ├── lint-nix.yaml │ └── lint-and-test.yaml ├── file-filters.yaml └── renovate.json5 ├── Cask ├── test ├── resources │ ├── indent-list.erts │ ├── indent-formals.erts │ ├── indent-inherit.erts │ ├── indent-attrset.erts │ ├── indent-let-in.erts │ └── indent-multiline-string.erts ├── scripts │ └── install-nix-grammar.el ├── utils.el ├── nix-ts-mode-indent-test.el ├── nix-ts-mode-font-lock-test.el └── highlighting-examples.nix ├── .gitignore ├── nix └── emacs-versions.nix ├── Makefile ├── flake.nix ├── README.org ├── flake.lock ├── nix-ts-mode.el └── LICENSE.md /.envrc: -------------------------------------------------------------------------------- 1 | dotenv_if_exists 2 | source_up 3 | use flake -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nix-community/nix-ts-mode -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "nix-ts-mode.el") 5 | 6 | (development 7 | (depends-on "ert-runner") 8 | (depends-on "package-lint")) -------------------------------------------------------------------------------- /test/resources/indent-list.erts: -------------------------------------------------------------------------------- 1 | Name: simple-list 2 | 3 | =-= 4 | [ 5 | foo 6 | "bar" 7 | 42 8 | ] 9 | =-= 10 | [ 11 | foo 12 | "bar" 13 | 42 14 | ] 15 | =-=-= 16 | -------------------------------------------------------------------------------- /test/resources/indent-formals.erts: -------------------------------------------------------------------------------- 1 | Name: simple-formals 2 | 3 | =-= 4 | { 5 | x, 6 | y, 7 | ... 8 | }: 42 9 | =-= 10 | { 11 | x, 12 | y, 13 | ... 14 | }: 42 15 | =-=-= 16 | -------------------------------------------------------------------------------- /test/scripts/install-nix-grammar.el: -------------------------------------------------------------------------------- 1 | (setq treesit-language-source-alist 2 | '((nix "https://github.com/nix-community/tree-sitter-nix"))) 3 | 4 | (treesit-install-language-grammar 'nix) 5 | -------------------------------------------------------------------------------- /test/resources/indent-inherit.erts: -------------------------------------------------------------------------------- 1 | Name: simple-inherit 2 | 3 | =-= 4 | pkgs: { 5 | inherit (pkgs) 6 | test 7 | hello 8 | ; 9 | } 10 | =-= 11 | pkgs: { 12 | inherit (pkgs) 13 | test 14 | hello 15 | ; 16 | } 17 | =-=-= 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nix 2 | result 3 | \#*.*\# 4 | 5 | # Direnv 6 | .direnv 7 | .env 8 | 9 | # pre-commit.nix 10 | .pre-commit-config.yaml 11 | 12 | # Emacs backup files 13 | *.*~ 14 | 15 | # Cask 16 | .cask 17 | 18 | # General 19 | .DS_Store 20 | *.dump -------------------------------------------------------------------------------- /.github/workflows/trunk.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - "trunk" 5 | 6 | jobs: 7 | lint-and-test: 8 | uses: ./.github/workflows/lint-and-test.yaml 9 | secrets: inherit 10 | permissions: 11 | contents: read 12 | pull-requests: read -------------------------------------------------------------------------------- /test/resources/indent-attrset.erts: -------------------------------------------------------------------------------- 1 | Name: simple-attrset 2 | 3 | =-= 4 | { 5 | hello = 6 | world; 7 | 8 | yes = 9 | "no"; 10 | 11 | pi = 12 | 3; 13 | } 14 | =-= 15 | { 16 | hello = 17 | world; 18 | 19 | yes = 20 | "no"; 21 | 22 | pi = 23 | 3; 24 | } 25 | =-=-= 26 | -------------------------------------------------------------------------------- /.github/file-filters.yaml: -------------------------------------------------------------------------------- 1 | # This is used by the action https://github.com/dorny/paths-filter 2 | 3 | package-sources: &package-sources 4 | - 'nix-ts-mode.el' 5 | - 'nix/emacs-versions.nix' 6 | 7 | test-sources: &test-sources 8 | - 'test/*.el' 9 | - 'test/**/*.el' 10 | 11 | nix-sources: &nix-sources 12 | - '*.nix' 13 | - '**/*.nix' -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | extends: [ 4 | "config:base", 5 | "group:all", 6 | "schedule:weekly" 7 | ], 8 | lockFileMaintenance: { 9 | enabled: true 10 | }, 11 | dependencyDashboard: true, 12 | nix: { 13 | enabled: true 14 | }, 15 | "github-actions": { 16 | enabled: true 17 | } 18 | } -------------------------------------------------------------------------------- /test/utils.el: -------------------------------------------------------------------------------- 1 | (defun reload-nix-buffers () 2 | (interactive) 3 | (mapc 4 | (lambda (buffer) 5 | (with-current-buffer buffer 6 | (unless buffer-read-only 7 | (when (derived-mode-p 'nix-ts-mode) 8 | (unload-feature 'nix-ts-mode t) 9 | (load-file "nix-ts-mode.el") 10 | (nix-ts-mode) 11 | (treesit-inspect-mode))))) 12 | (buffer-list))) 13 | -------------------------------------------------------------------------------- /nix/emacs-versions.nix: -------------------------------------------------------------------------------- 1 | # Adjust to add new Emacs versions to CI 2 | # Emacs 29.x support has been removed due to the use of new font-lock faces 3 | # Also https://search.nixos.org/packages?channel=25.05&query=emacs 4 | # shows that the lowest currently supported version of emacs in nixpkgs is 30.2 5 | [ 6 | "29.1" 7 | "29.2" 8 | "29.3" 9 | "30.1" 10 | "30.2" 11 | "snapshot" 12 | "release-snapshot" 13 | ] 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "trunk" 7 | 8 | jobs: 9 | lint-and-test: 10 | uses: ./.github/workflows/lint-and-test.yaml 11 | secrets: inherit 12 | permissions: 13 | contents: read 14 | pull-requests: read 15 | 16 | collect: 17 | runs-on: ubuntu-latest 18 | needs: [lint-and-test] 19 | steps: 20 | - run: true 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | EMACS ?= emacs 4 | 5 | lisp: 6 | EMACS=$(EMACS) cask install \ 7 | EMACS=$(EMACS) cask build 8 | 9 | clean: 10 | rm -rf .cask 11 | 12 | test: lisp 13 | EMACS=$(EMACS) cask exec ert-runner --reporter ert 14 | 15 | lint: lisp 16 | EMACS=$(EMACS) cask emacs \ 17 | -batch \ 18 | -l package-lint \ 19 | --eval "(setq package-lint-batch-fail-on-warnings nil)" \ 20 | -f package-lint-batch-and-exit nix-ts-mode.el -------------------------------------------------------------------------------- /test/resources/indent-let-in.erts: -------------------------------------------------------------------------------- 1 | Name: empty-let-in 2 | 3 | =-= 4 | let 5 | in z 6 | =-= 7 | let 8 | in z 9 | =-=-= 10 | 11 | Name: single-let-in 12 | 13 | =-= 14 | let 15 | x = y; 16 | in z 17 | =-= 18 | let 19 | x = y; 20 | in z 21 | =-=-= 22 | 23 | Name: nested-let-in 24 | 25 | =-= 26 | let 27 | x = let 28 | a = b; 29 | in c; 30 | in z 31 | =-= 32 | let 33 | x = let 34 | a = b; 35 | in c; 36 | in z 37 | =-=-= 38 | 39 | Name: let-in-semicolon 40 | 41 | =-= 42 | let 43 | x = let 44 | a = b; 45 | in 46 | c 47 | ; 48 | in z 49 | =-= 50 | let 51 | x = let 52 | a = b; 53 | in 54 | c 55 | ; 56 | in z 57 | =-=-= 58 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | pull-requests: read 16 | steps: 17 | - name: Generate changelog 18 | id: github_release 19 | uses: mikepenz/release-changelog-builder-action@v6 20 | env: 21 | ignorePreReleases: true 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Publish release 25 | uses: softprops/action-gh-release@v2 26 | with: 27 | body: ${{steps.github_release.outputs.changelog}} -------------------------------------------------------------------------------- /test/resources/indent-multiline-string.erts: -------------------------------------------------------------------------------- 1 | Name: simple-multiline-string 2 | 3 | =-= 4 | { 5 | a = '' 6 | hi [ 7 | test 8 | ] 9 | ''; 10 | } 11 | =-= 12 | { 13 | a = '' 14 | hi [ 15 | test 16 | ] 17 | ''; 18 | } 19 | =-=-= 20 | 21 | Name: multiline-string-with-interpolations 22 | 23 | =-= 24 | { 25 | b = "hi"; 26 | a = '' 27 | ${b} 28 | but also 29 | bye 30 | ''; 31 | } 32 | =-= 33 | { 34 | b = "hi"; 35 | a = '' 36 | ${b} 37 | but also 38 | bye 39 | ''; 40 | } 41 | =-=-= 42 | 43 | Name: multiline-string-with-escaped-interpolations 44 | 45 | =-= 46 | { 47 | a = '' 48 | what=''${1:-hi} 49 | echo "$what" 50 | ''; 51 | } 52 | =-= 53 | { 54 | a = '' 55 | what=''${1:-hi} 56 | echo "$what" 57 | ''; 58 | } 59 | =-=-= 60 | -------------------------------------------------------------------------------- /.github/workflows/lint-package.yaml: -------------------------------------------------------------------------------- 1 | name: Lint package 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | emacs-versions: 7 | required: true 8 | type: string 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | emacs_version: ${{ fromJson(inputs.emacs-versions) }} 21 | 22 | steps: 23 | - uses: actions/checkout@v6.0.0 24 | - uses: purcell/setup-emacs@master 25 | with: 26 | version: ${{ matrix.emacs_version }} 27 | - uses: conao3/setup-cask@master 28 | with: 29 | version: snapshot 30 | - run: make lint 31 | 32 | collect-lint: 33 | needs: [lint] 34 | if: ${{ always() }} 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Check all matrix job status 38 | if: >- 39 | ${{ 40 | contains(needs.*.result, 'failure') 41 | || contains(needs.*.result, 'cancelled') 42 | || contains(needs.*.result, 'skipped') 43 | }} 44 | run: exit 1 -------------------------------------------------------------------------------- /.github/workflows/test-package.yaml: -------------------------------------------------------------------------------- 1 | name: Test package 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | emacs-versions: 7 | required: true 8 | type: string 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | emacs_version: ${{ fromJson(inputs.emacs-versions) }} 17 | 18 | steps: 19 | - uses: actions/checkout@v6.0.0 20 | - uses: purcell/setup-emacs@master 21 | with: 22 | version: ${{ matrix.emacs_version }} 23 | - uses: conao3/setup-cask@master 24 | with: 25 | version: snapshot 26 | - name: Install Nix grammar 27 | run: | 28 | emacs -batch -l test/scripts/install-nix-grammar.el 29 | - name: Run ERT units 30 | run: make test 31 | - name: Cleanup 32 | run: make clean 33 | 34 | collect-tests: 35 | needs: [test] 36 | if: ${{ always() }} 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Check all matrix job status 40 | if: >- 41 | ${{ 42 | contains(needs.*.result, 'failure') 43 | || contains(needs.*.result, 'cancelled') 44 | || contains(needs.*.result, 'skipped') 45 | }} 46 | run: exit 1 47 | -------------------------------------------------------------------------------- /.github/workflows/lint-nix.yaml: -------------------------------------------------------------------------------- 1 | name: Lint nix 2 | 3 | on: 4 | workflow_call: 5 | 6 | permissions: {} 7 | 8 | jobs: 9 | nixpkgs-fmt: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | steps: 14 | - uses: DeterminateSystems/nix-installer-action@v21 15 | - uses: DeterminateSystems/magic-nix-cache-action@v13 16 | - uses: actions/checkout@v6.0.0 17 | - name: Check formatting 18 | run: | 19 | nix run --inputs-from . nixpkgs#nixpkgs-fmt -- --check . 20 | 21 | statix: 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: read 25 | steps: 26 | - uses: DeterminateSystems/nix-installer-action@v21 27 | - uses: DeterminateSystems/magic-nix-cache-action@v13 28 | - uses: actions/checkout@v6.0.0 29 | - name: Static code analysis 30 | run: | 31 | nix run --inputs-from . nixpkgs#statix -- check . -i test/highlighting-examples.nix 32 | 33 | deadnix: 34 | runs-on: ubuntu-latest 35 | permissions: 36 | contents: read 37 | steps: 38 | - uses: DeterminateSystems/nix-installer-action@v21 39 | - uses: DeterminateSystems/magic-nix-cache-action@v13 40 | - uses: actions/checkout@v6.0.0 41 | - name: Check for dead code 42 | run: | 43 | nix run --inputs-from . nixpkgs#deadnix -- -f --exclude test/highlighting-examples.nix 44 | 45 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "An Emacs major mode for editing Nix expressions, powered by tree-sitter."; 3 | 4 | outputs = 5 | { nixpkgs-unstable 6 | , pre-commit-nix 7 | , ... 8 | }: 9 | let 10 | supportedSystems = [ 11 | "aarch64-darwin" 12 | "aarch64-linux" 13 | "x86_64-darwin" 14 | "x86_64-linux" 15 | ]; 16 | forAllSystems = nixpkgs-unstable.lib.genAttrs supportedSystems; 17 | in 18 | { 19 | supportedEmacsVersions = import ./nix/emacs-versions.nix; 20 | 21 | devShells = forAllSystems ( 22 | system: 23 | let 24 | pre-commit-check = pre-commit-nix.lib.${system}.run { 25 | src = ./.; 26 | 27 | hooks = { 28 | nixpkgs-fmt.enable = true; 29 | deadnix = { 30 | enable = true; 31 | excludes = [ "test/highlighting-examples.nix" ]; 32 | }; 33 | statix = { 34 | enable = true; 35 | settings.ignore = [ "test/highlighting-examples.nix" ]; 36 | }; 37 | }; 38 | }; 39 | in 40 | { 41 | default = nixpkgs-unstable.legacyPackages.${system}.mkShell { 42 | name = "nix-ts-mode-shell"; 43 | 44 | packages = with nixpkgs-unstable.legacyPackages.${system}; [ 45 | nixpkgs-fmt 46 | cask 47 | python312 48 | ]; 49 | 50 | shellHook = '' 51 | ${pre-commit-check.shellHook} 52 | ''; 53 | }; 54 | } 55 | ); 56 | 57 | formatter = forAllSystems (system: nixpkgs-unstable.legacyPackages.${system}.nixpkgs-fmt); 58 | }; 59 | 60 | inputs = { 61 | nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 62 | pre-commit-nix.url = "github:cachix/pre-commit-hooks.nix"; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-test.yaml: -------------------------------------------------------------------------------- 1 | name: Lint and test 2 | 3 | permissions: {} 4 | 5 | on: 6 | workflow_call: 7 | 8 | jobs: 9 | emacs-versions: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | EMACS_VERSIONS: ${{ steps.set-emacs-versions.outputs.EMACS_VERSIONS }} 13 | permissions: 14 | contents: read 15 | steps: 16 | - uses: actions/checkout@v6.0.0 17 | - uses: DeterminateSystems/nix-installer-action@v21 18 | - uses: DeterminateSystems/magic-nix-cache-action@v13 19 | - id: set-emacs-versions 20 | run: | 21 | EMACS_VERSIONS=$(nix eval --json .#supportedEmacsVersions) 22 | echo "EMACS_VERSIONS=$EMACS_VERSIONS" >> "$GITHUB_OUTPUT" 23 | 24 | file-changes: 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: read 28 | pull-requests: read 29 | outputs: 30 | package-sources: ${{ steps.changes.outputs.package-sources }} 31 | test-sources: ${{ steps.changes.outputs.test-sources }} 32 | nix-sources: ${{ steps.changes.outputs.nix-sources }} 33 | steps: 34 | - uses: actions/checkout@v6.0.0 35 | - uses: dorny/paths-filter@v3 36 | id: changes 37 | with: 38 | filters: .github/file-filters.yaml 39 | 40 | lint-package: 41 | uses: ./.github/workflows/lint-package.yaml 42 | permissions: 43 | contents: read 44 | needs: [emacs-versions, file-changes] 45 | if: needs.file-changes.outputs.package-sources == 'true' 46 | secrets: inherit 47 | with: 48 | emacs-versions: ${{ needs.emacs-versions.outputs.EMACS_VERSIONS }} 49 | 50 | test-package: 51 | uses: ./.github/workflows/test-package.yaml 52 | permissions: 53 | contents: read 54 | needs: [emacs-versions, file-changes] 55 | if: needs.file-changes.outputs.package-sources == 'true' 56 | secrets: inherit 57 | with: 58 | emacs-versions: ${{ needs.emacs-versions.outputs.EMACS_VERSIONS }} 59 | 60 | lint-nix: 61 | uses: ./.github/workflows/lint-nix.yaml 62 | if: needs.file-changes.outputs.nix-sources == 'true' 63 | permissions: 64 | contents: read 65 | needs: file-changes 66 | secrets: inherit -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: nix-ts-mode 2 | 3 | #+PROPERTY: LOGGING nil 4 | 5 | [[https://melpa.org/#/nix-ts-mode][file:https://melpa.org/packages/nix-ts-mode-badge.svg]] 6 | [[https://stable.melpa.org/#/nix-ts-mode][file:https://stable.melpa.org/packages/nix-ts-mode-badge.svg]] 7 | [[https://github.com/nix-community/nix-ts-mode/actions/workflows/trunk.yaml][https://img.shields.io/github/actions/workflow/status/nix-community/nix-ts-mode/trunk.yaml.svg?label=Trunk&event=push&branch=trunk]] 8 | 9 | An Emacs major mode for editing Nix expressions, powered by the built-in 10 | tree-sitter support in Emacs 29+ and the community-maintained [[https://github.com/nix-community/tree-sitter-nix][Nix tree-sitter grammar]]. 11 | 12 | *Features:* 13 | - Syntax highlighting for all Nix language constructs 14 | - Semantic distinctions (variables vs properties, function calls vs definitions) 15 | - 100% tree-sitter-nix grammar coverage 16 | - Comprehensive tests 17 | 18 | *Requirements:* Emacs 29+ with tree-sitter support, 30.x includes more improvements. 19 | 20 | ** Usage 21 | 22 | ~nix-ts-mode~ provides comprehensive syntax highlighting and indentation for Nix files. 23 | After installing, enable the mode for Nix files like so: 24 | 25 | #+BEGIN_SRC emacs-lisp 26 | (require 'nix-ts-mode) 27 | (add-to-list 'auto-mode-alist '("\\.nix\\'" . nix-ts-mode)) 28 | #+END_SRC 29 | 30 | Or with ~use-package~: 31 | 32 | #+BEGIN_SRC emacs-lisp 33 | (use-package nix-ts-mode 34 | :mode "\\.nix\\'") 35 | #+END_SRC 36 | 37 | *** Maximum Highlighting 38 | 39 | For the best syntax highlighting experience with the widest selection of font-lock faces, set the font-lock level to 4 (maximum): 40 | 41 | #+BEGIN_SRC emacs-lisp 42 | ;; Add to your init.el before loading nix-ts-mode 43 | (setq treesit-font-lock-level 4) 44 | #+END_SRC 45 | 46 | This enables: 47 | - Fine-grained semantic distinctions (variables vs properties) 48 | - Function parameter highlighting 49 | - Punctuation and bracket highlighting 50 | - Complete visual clarity 51 | - below is a list of what is included at each level 52 | - ~test/highlighting-example.nix~ can be use to see the effects of the levels. 53 | 54 | **** Level 1 (Essential) 55 | - comment 56 | - builtin 57 | - constant 58 | 59 | **** Level 2 (Literals) 60 | - string 61 | - path 62 | - uri 63 | 64 | **** Level 3 (Semantic) 65 | - number 66 | - operator 67 | - definition 68 | - function-call 69 | - keyword 70 | 71 | **** Level 4 (Decorative) 72 | - parameter 73 | - property 74 | - variable 75 | - bracket 76 | - delimiter 77 | - ellipses 78 | - punctuation 79 | - **paren-base** (override) 80 | - **parameter-atpattern** (override) 81 | - error 82 | -------------------------------------------------------------------------------- /test/nix-ts-mode-indent-test.el: -------------------------------------------------------------------------------- 1 | ;;; nix-ts-mode-indent-test.el --- Indentation tests for nix-ts-mode. -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2024 Andreas Fuchs 4 | 5 | ;; Author: Andreas Fuchs 6 | ;; Keywords: nix 7 | ;; Package-Requires: ((emacs "29.1")) 8 | 9 | ;; This file is NOT part of GNU Emacs. 10 | 11 | ;;; Commentary: 12 | 13 | 14 | ;; Tests for tree-sitter powered indentation in `nix-ts-mode`. 15 | 16 | ;;; Code: 17 | (require 'ert) 18 | (require 'ert-x) 19 | (require 'nix-ts-mode) 20 | 21 | (defmacro with-nix-buffer-contents (&rest body) 22 | "Run `BODY` in the context of a new buffer set to `nix-ts-mode`." 23 | `(with-temp-buffer 24 | (delay-mode-hooks (nix-ts-mode)) 25 | ,@body)) 26 | 27 | (defun check-indentation (contents) 28 | "Reindent CONTENTS according to nix-ts-mode's rules and check that it matches. 29 | 30 | CONTENT is a correctly-indented nix expression; all its lines' leading 31 | whitespace is stripped, then re-indented and checked that the 32 | output is identical to the given expression." 33 | (let ((dedented (replace-regexp-in-string "^\\s+" "" contents))) 34 | (insert dedented) 35 | (indent-region (point-min) (point-max)) 36 | (should (equal (buffer-substring (point-min) (point-max)) 37 | contents)))) 38 | 39 | ;;; Features 40 | 41 | (ert-deftest nix-multiline-string () 42 | (ert-test-erts-file (ert-resource-file "indent-multiline-string.erts") 43 | (lambda () 44 | (nix-ts-mode) 45 | (indent-region (point-min) (point-max))))) 46 | 47 | (ert-deftest nix-let-in () 48 | (ert-test-erts-file (ert-resource-file "indent-let-in.erts") 49 | (lambda () 50 | (nix-ts-mode) 51 | (indent-region (point-min) (point-max))))) 52 | 53 | (ert-deftest nix-formals () 54 | (ert-test-erts-file (ert-resource-file "indent-formals.erts") 55 | (lambda () 56 | (nix-ts-mode) 57 | (indent-region (point-min) (point-max))))) 58 | 59 | (ert-deftest nix-list () 60 | (ert-test-erts-file (ert-resource-file "indent-list.erts") 61 | (lambda () 62 | (nix-ts-mode) 63 | (indent-region (point-min) (point-max))))) 64 | 65 | (ert-deftest nix-attrset () 66 | (ert-test-erts-file (ert-resource-file "indent-attrset.erts") 67 | (lambda () 68 | (nix-ts-mode) 69 | (indent-region (point-min) (point-max))))) 70 | 71 | (ert-deftest nix-inherit () 72 | (ert-test-erts-file (ert-resource-file "indent-inherit.erts") 73 | (lambda () 74 | (nix-ts-mode) 75 | (indent-region (point-min) (point-max))))) 76 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1747046372, 7 | "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "gitignore": { 20 | "inputs": { 21 | "nixpkgs": [ 22 | "pre-commit-nix", 23 | "nixpkgs" 24 | ] 25 | }, 26 | "locked": { 27 | "lastModified": 1709087332, 28 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 29 | "owner": "hercules-ci", 30 | "repo": "gitignore.nix", 31 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "hercules-ci", 36 | "repo": "gitignore.nix", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1754340878, 43 | "narHash": "sha256-lgmUyVQL9tSnvvIvBp7x1euhkkCho7n3TMzgjdvgPoU=", 44 | "owner": "NixOS", 45 | "repo": "nixpkgs", 46 | "rev": "cab778239e705082fe97bb4990e0d24c50924c04", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "NixOS", 51 | "ref": "nixpkgs-unstable", 52 | "repo": "nixpkgs", 53 | "type": "github" 54 | } 55 | }, 56 | "nixpkgs-unstable": { 57 | "locked": { 58 | "lastModified": 1757034884, 59 | "narHash": "sha256-PgLSZDBEWUHpfTRfFyklmiiLBE1i1aGCtz4eRA3POao=", 60 | "owner": "NixOS", 61 | "repo": "nixpkgs", 62 | "rev": "ca77296380960cd497a765102eeb1356eb80fed0", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "owner": "NixOS", 67 | "ref": "nixpkgs-unstable", 68 | "repo": "nixpkgs", 69 | "type": "github" 70 | } 71 | }, 72 | "pre-commit-nix": { 73 | "inputs": { 74 | "flake-compat": "flake-compat", 75 | "gitignore": "gitignore", 76 | "nixpkgs": "nixpkgs" 77 | }, 78 | "locked": { 79 | "lastModified": 1757239681, 80 | "narHash": "sha256-E9spYi9lxm2f1zWQLQ7xQt8Xs2nWgr1T4QM7ZjLFphM=", 81 | "owner": "cachix", 82 | "repo": "pre-commit-hooks.nix", 83 | "rev": "ab82ab08d6bf74085bd328de2a8722c12d97bd9d", 84 | "type": "github" 85 | }, 86 | "original": { 87 | "owner": "cachix", 88 | "repo": "pre-commit-hooks.nix", 89 | "type": "github" 90 | } 91 | }, 92 | "root": { 93 | "inputs": { 94 | "nixpkgs-unstable": "nixpkgs-unstable", 95 | "pre-commit-nix": "pre-commit-nix" 96 | } 97 | } 98 | }, 99 | "root": "root", 100 | "version": 7 101 | } 102 | -------------------------------------------------------------------------------- /test/nix-ts-mode-font-lock-test.el: -------------------------------------------------------------------------------- 1 | ;;; nix-ts-mode-font-lock-tests.el --- Font lock tests for `nix-ts-mode`. -*- lexical-binding: t -*- 2 | 3 | ;; Maintainer: Remi Gelinas 4 | ;; Homepage: https://github.com/remi-gelinas/nix-ts-mode 5 | ;; Version: 0.1.2 6 | ;; Keywords: nix 7 | ;; Package-Requires: ((emacs "29.1")) 8 | 9 | ;; This file is NOT part of GNU Emacs. 10 | 11 | ;;; Commentary: 12 | 13 | 14 | ;; Tests for tree-sitter powered font-locking in `nix-ts-mode`. 15 | 16 | ;;; Code: 17 | (require 'ert) 18 | (require 'nix-ts-mode) 19 | 20 | (defmacro with-nix-buffer (&rest body) 21 | "Run `BODY` in the context of a new buffer set to `nix-ts-mode`." 22 | `(with-temp-buffer 23 | (delay-mode-hooks (nix-ts-mode)) 24 | ,@body)) 25 | 26 | (defun check-faces (content pairs) 27 | "" 28 | (with-nix-buffer 29 | (insert content) 30 | (save-excursion 31 | (setq-local treesit-font-lock-level 4) 32 | (treesit-font-lock-recompute-features) 33 | (treesit-font-lock-fontify-region (point-min) (point-max) t)) 34 | (dolist (pair pairs) 35 | (goto-char (point-min)) 36 | (cl-destructuring-bind (string face) pair 37 | (let ((case-fold-search nil)) 38 | (search-forward string)) 39 | (let* ((beg (match-beginning 0)) 40 | (end (match-end 0)) 41 | (prop-ranges (object-intervals (buffer-substring beg end))) 42 | (face-ranges (cl-loop for range in prop-ranges 43 | for face = (plist-get (elt range 2) 'face) 44 | when face 45 | collect (list (elt range 0) (elt range 1) face)))) 46 | (should (equal `(,string ,face-ranges) 47 | `(,string ((0 ,(- end beg) ,face)))))))))) 48 | 49 | ;; Features 50 | 51 | (ert-deftest nix-ts-bracket () 52 | (check-faces 53 | "{ 54 | test = [ 55 | (doFunc \"test\") 56 | ]; 57 | }" 58 | '(("(" font-lock-bracket-face) 59 | (")" font-lock-bracket-face) 60 | ("[" font-lock-bracket-face) 61 | ("]" font-lock-bracket-face) 62 | ("{" font-lock-bracket-face) 63 | ("}" font-lock-bracket-face)))) 64 | 65 | (ert-deftest nix-ts-comment () 66 | (check-faces "# This is a Nix comment, alright" 67 | '(("Nix comment" font-lock-comment-face)))) 68 | 69 | (ert-deftest nix-ts-delimiter () 70 | (check-faces 71 | "{ attribute.attribute = {param, ...}: {}; }" 72 | '(("." font-lock-delimiter-face) 73 | ("," font-lock-delimiter-face) 74 | (";" font-lock-delimiter-face)))) 75 | 76 | (ert-deftest nix-ts-keyword () 77 | (check-faces " 78 | let 79 | pkgs = { 80 | test = \"\"; 81 | }; 82 | a = x.y or z; 83 | in rec { 84 | inherit pkgs; 85 | test = with pkgs; test; 86 | }" 87 | '(("let" font-lock-keyword-face) 88 | ("inherit" font-lock-keyword-face) 89 | ("in" font-lock-keyword-face) 90 | ("with" font-lock-keyword-face) 91 | ("rec" font-lock-keyword-face) 92 | ("or" font-lock-keyword-face)))) 93 | 94 | 95 | (ert-deftest nix-ts-error () 96 | (check-faces " 97 | let } 98 | " 99 | '(("let" font-lock-warning-face) 100 | ("}" font-lock-warning-face)))) 101 | 102 | (ert-deftest nix-ts-escape-sequence () 103 | (check-faces "\"test\\nstring\\t\\\"escaped\\\"\"" 104 | '(("\\n" font-lock-escape-face) 105 | ("\\t" font-lock-escape-face) 106 | ("\\\"" font-lock-escape-face)))) 107 | 108 | (ert-deftest nix-ts-uri () 109 | ;; Note: Unquoted URIs are deprecated in modern Nix, so we test with quoted strings 110 | ;; which still get uri_expression node type in older parsers, or string_expression in newer ones 111 | (check-faces "{ 112 | url = \"https://example.com/file.tar.gz\"; 113 | }" 114 | '(("https://example.com/file.tar.gz" font-lock-string-face)))) 115 | 116 | (ert-deftest nix-ts-paths () 117 | (check-faces "{ 118 | path1 = ./relative/path; 119 | path2 = /absolute/path; 120 | path3 = ~/home/path; 121 | path4 = ; 122 | }" 123 | '(("./relative/path" font-lock-constant-face) 124 | ("/absolute/path" font-lock-constant-face) 125 | ("~/home/path" font-lock-constant-face) 126 | ("" font-lock-constant-face)))) 127 | 128 | (ert-deftest nix-ts-function-parameters () 129 | (check-faces "{ x, y ? 5, z, ... }: x + y" 130 | '(("x" font-lock-variable-name-face) 131 | ("y" font-lock-variable-name-face) 132 | ("z" font-lock-variable-name-face)))) 133 | 134 | (ert-deftest nix-ts-universal-parameter () 135 | (check-faces "x: x + 1" 136 | '(("x" font-lock-variable-name-face)))) 137 | 138 | (ert-deftest nix-ts-at-pattern () 139 | (check-faces "{ 140 | forward = args@{ x, y }: args.x; 141 | reverse = { foo, bar }@opts: opts.baz; 142 | }" 143 | '(;; At-pattern binding gets type-face (the "base object" containing params) 144 | ("args" font-lock-type-face) 145 | ("opts" font-lock-type-face) 146 | ;; @ operator 147 | ("@" font-lock-operator-face) 148 | ;; Destructured parameters get variable-name-face 149 | ("x" font-lock-variable-name-face) 150 | ("y" font-lock-variable-name-face) 151 | ("foo" font-lock-variable-name-face) 152 | ("bar" font-lock-variable-name-face)))) 153 | 154 | (ert-deftest nix-ts-function-call () 155 | (check-faces "{ 156 | result = map (x: x + 1) list; 157 | result2 = builtins.filter (x: x > 0) list; 158 | result3 = customFunc arg; 159 | }" 160 | '(;; map is a builtin, so it gets builtin face, not function-call 161 | ("map" font-lock-builtin-face) 162 | ;; filter with builtins prefix gets builtin face 163 | ("filter" font-lock-builtin-face) 164 | ;; customFunc is not a builtin, so it gets function-call face 165 | ("customFunc" font-lock-function-call-face)))) 166 | 167 | (ert-deftest nix-ts-function-definition () 168 | (check-faces "{ 169 | myFunction = x: y: x + y; 170 | anotherFunc = { a, b }: a + b; 171 | }" 172 | '(("myFunction" font-lock-function-name-face) 173 | ("anotherFunc" font-lock-function-name-face)))) 174 | 175 | (ert-deftest nix-ts-property-access () 176 | (check-faces "{ 177 | value = pkgs.hello; 178 | nested = lib.mkOption { }; 179 | justAccess = config.services.nginx.enable; 180 | }" 181 | '(;; Base variables in select get type-face for visual distinction 182 | ("pkgs" font-lock-type-face) 183 | ("lib" font-lock-type-face) 184 | ("config" font-lock-type-face) 185 | ;; Property members get property-use-face 186 | ("hello" font-lock-property-use-face) 187 | ;; mkOption gets function-call-face because it's being called 188 | ("mkOption" font-lock-function-call-face) 189 | ;; These are just property access, not function calls 190 | ("enable" font-lock-property-use-face)))) 191 | 192 | (ert-deftest nix-ts-builtin-functions () 193 | (check-faces "{ 194 | x = map toString [ 1 2 3 ]; 195 | y = builtins.head list; 196 | z = __add 1 2; 197 | }" 198 | '(("map" font-lock-builtin-face) 199 | ("toString" font-lock-builtin-face) 200 | ("head" font-lock-builtin-face) 201 | ("__add" font-lock-builtin-face)))) 202 | 203 | (ert-deftest nix-ts-constants () 204 | (check-faces "{ 205 | x = null; 206 | y = true; 207 | z = false; 208 | sys = builtins.currentSystem; 209 | }" 210 | '(("null" font-lock-constant-face) 211 | ("true" font-lock-constant-face) 212 | ("false" font-lock-constant-face) 213 | ("builtins" font-lock-constant-face) 214 | ("currentSystem" font-lock-builtin-face)))) 215 | 216 | (ert-deftest nix-ts-import-keyword () 217 | (check-faces "import ./file.nix" 218 | '(("import" font-lock-keyword-face)))) 219 | 220 | (ert-deftest nix-ts-operators () 221 | (check-faces "args@{ x ? 5, y }: x + y" 222 | '(("@" font-lock-operator-face) 223 | ("?" font-lock-operator-face) 224 | ("+" font-lock-operator-face)))) 225 | 226 | (ert-deftest nix-ts-numbers () 227 | (check-faces "{ 228 | int = 42; 229 | float = 3.14; 230 | negInt = -10; 231 | negFloat = -2.5; 232 | }" 233 | '(("42" font-lock-number-face) 234 | ("3.14" font-lock-number-face) 235 | ("10" font-lock-number-face) 236 | ("2.5" font-lock-number-face)))) 237 | 238 | (ert-deftest nix-ts-string-interpolation () 239 | (check-faces "\"hello ${name} world\"" 240 | '(("${" font-lock-punctuation-face) 241 | ("}" font-lock-punctuation-face)))) 242 | 243 | (ert-deftest nix-ts-inherited-attrs () 244 | (check-faces "{ 245 | inherit (pkgs) hello world; 246 | inherit x y z; 247 | }" 248 | '(("inherit" font-lock-keyword-face) 249 | ;; inherit (SOURCE) - source gets type-face for visual distinction 250 | ("pkgs" font-lock-type-face) 251 | ;; inherit (pkgs) hello - hello is a PROPERTY being accessed 252 | ("hello" font-lock-property-use-face) 253 | ("world" font-lock-property-use-face) 254 | ;; inherit x y z - these are VARIABLE BINDINGS being created 255 | ("x" font-lock-variable-name-face) 256 | ("y" font-lock-variable-name-face)))) 257 | 258 | (ert-deftest nix-ts-binding-attrs () 259 | (check-faces "{ 260 | name = \"value\"; 261 | nested.attr = 42; 262 | }" 263 | '(("name" font-lock-property-name-face) 264 | ("nested" font-lock-property-name-face)))) 265 | 266 | (ert-deftest nix-ts-has-attr-expression () 267 | (check-faces "{ 268 | hasX = x ? y; 269 | hasAttr = obj ? attr; 270 | }" 271 | '(("?" font-lock-operator-face)))) 272 | 273 | (ert-deftest nix-ts-dollar-escape () 274 | ;; In indented strings, ''$ escapes the interpolation 275 | ;; The tree structure is: dollar_escape contains just the '' part 276 | (with-nix-buffer 277 | (insert "'' test ''${notInterpolated} ''") 278 | (save-excursion 279 | (setq-local treesit-font-lock-level 4) 280 | (treesit-font-lock-recompute-features) 281 | (treesit-font-lock-fontify-region (point-min) (point-max) t)) 282 | ;; Find the middle '' which is the dollar_escape 283 | (goto-char (point-min)) 284 | (search-forward "test ") 285 | (let* ((beg (point)) 286 | (end (+ beg 2)) 287 | (face (get-text-property beg 'face))) 288 | (should (equal face 'font-lock-escape-face))))) 289 | 290 | (ert-deftest nix-ts-let-attrset () 291 | (check-faces "let { x = 1; body = x; }" 292 | '(("let" font-lock-keyword-face)))) 293 | 294 | (provide 'nix-ts-mode-font-lock-tests) 295 | ;;; nix-ts-mode-font-lock-tests.el ends here 296 | -------------------------------------------------------------------------------- /test/highlighting-examples.nix: -------------------------------------------------------------------------------- 1 | # Comprehensive syntax highlighting examples for nix-ts-mode 2 | # This file demonstrates complete coverage of all Nix language constructs 3 | # and showcases the visual distinctions in property chains and semantic highlighting 4 | 5 | { 6 | # ================================================================= 7 | # SECTION 1: PROPERTY CHAIN HIGHLIGHTING 8 | # ================================================================= 9 | # Demonstrates visual distinction between base variables and property members 10 | 11 | propertyChains = { 12 | # Simple property access (2 parts) 13 | # Base variable gets type-face, members get property-use-face 14 | package = pkgs.hello; 15 | 16 | # Nested property access (3 parts) 17 | service = config.services.nginx; 18 | 19 | # Deep property access (4 parts) 20 | output = system.build.toplevel.outPath; 21 | 22 | # Very deep property access (5+ parts) 23 | # Base is visually distinct even in long chains 24 | deepNesting = lib.modules.evalModules.options.enable.default; 25 | 26 | # Property access on different base expressions 27 | fromVariable = myVar.property.nested; 28 | fromBuiltin = builtins.attrNames value; 29 | fromFunction = (getConfig args).services.nginx.enable; 30 | 31 | # Comparison: standalone vs property chains 32 | standalone = myVariable; # Variable-use-face (standalone) 33 | inChain = obj.property; # Base: type-face, property: property-use-face 34 | }; 35 | 36 | # ================================================================= 37 | # SECTION 2: NEWLY ADDED NODE TYPE COVERAGE 38 | # ================================================================= 39 | # Features that complete 100% tree-sitter-nix grammar coverage 40 | 41 | # 1. has_attr_expression - Test if attribute exists with ? operator 42 | hasAttrExamples = { 43 | checkSimple = x ? y; 44 | checkNested = config ? services; 45 | checkDeep = obj ? attr.nested.deep; 46 | 47 | # Common use case: optional with fallback 48 | value = attrs.myAttr or "default"; 49 | }; 50 | 51 | # 2. dollar_escape - Escape interpolation in indented strings 52 | dollarEscapeExamples = { 53 | # In indented strings, ''$ escapes the ${ interpolation 54 | bashScript = '' 55 | # This won't be interpolated: 56 | echo "Variable: ''${MY_VAR}" 57 | 58 | # But this will be interpolated: 59 | echo "Nix var: ${nixVar}" 60 | ''; 61 | 62 | literalBraces = '' 63 | Use ''${...} for literal dollar-brace in bash/shell scripts 64 | ''; 65 | }; 66 | 67 | # 3. let_attrset_expression - Deprecated syntax (still supported) 68 | # Modern Nix prefers 'rec { }' or 'let ... in ...' instead 69 | oldStyleLet = let { 70 | x = 1; 71 | y = 2; 72 | body = x + y; # Special 'body' attribute is returned 73 | }; 74 | 75 | # ================================================================= 76 | # SECTION 3: KEYWORDS AND CONTROL FLOW 77 | # ================================================================= 78 | 79 | keywords = { 80 | # if/then/else 81 | conditionalExample = if true then "yes" else "no"; 82 | 83 | # assert 84 | assertExample = 85 | assert x > 0; 86 | x + 1; 87 | 88 | # with 89 | withExample = with pkgs; [ 90 | hello 91 | world 92 | ]; 93 | 94 | # let/in 95 | letExample = 96 | let 97 | x = 1; 98 | y = 2; 99 | in 100 | x + y; 101 | 102 | # rec 103 | recExample = rec { 104 | x = 1; 105 | y = x + 1; 106 | z = y + 1; 107 | }; 108 | 109 | # inherit 110 | inheritExample = { 111 | inherit x y z; 112 | inherit (pkgs) hello world; 113 | inherit (lib) mkOption types; 114 | }; 115 | 116 | # Special keywords: import, throw, abort 117 | importExample = import ./some-file.nix; 118 | throwExample = x: if x < 0 then throw "negative" else x; 119 | abortExample = if false then abort "error" else "ok"; 120 | 121 | # or operator (default values) 122 | orExample = obj.attr or defaultValue; 123 | }; 124 | 125 | # ================================================================= 126 | # SECTION 4: FUNCTIONS 127 | # ================================================================= 128 | 129 | functions = { 130 | # Function definitions (function-name-face) 131 | simpleFunction = x: x + 1; 132 | multiArgFunction = x: y: x + y; 133 | 134 | # Formal parameters (variable-name-face for parameters) 135 | formalFunction = 136 | { a 137 | , b ? 5 138 | , c 139 | , ... 140 | }: 141 | a + b + c; 142 | 143 | # @ pattern (variable-name-face for args) 144 | atPatternFunction = args@{ x, y, ... }: args.x + y; 145 | 146 | # Function calls (function-call-face) 147 | simpleCall = myFunc arg; 148 | multiCall = func arg1 arg2 arg3; 149 | 150 | # Builtin function calls (builtin-face) 151 | mapExample = map toString [ 152 | 1 153 | 2 154 | 3 155 | ]; 156 | filterExample = filter (x: x > 0) list; 157 | builtinsMap = builtins.map (x: x * 2) list; 158 | builtinsFilter = builtins.filter isAttrs items; 159 | 160 | # Primop functions (__* functions - builtin-face) 161 | addExample = __add 1 2; 162 | subExample = __sub 10 5; 163 | concatExample = __concatLists [ 164 | [ 1 ] 165 | [ 2 ] 166 | [ 3 ] 167 | ]; 168 | isAttrsExample = __isAttrs { }; 169 | 170 | # Property-based function calls (function-call-face for last segment) 171 | propertyCall = lib.mkOption { }; 172 | deepCall = config.services.nginx.enable; 173 | }; 174 | 175 | # ================================================================= 176 | # SECTION 5: LITERALS AND CONSTANTS 177 | # ================================================================= 178 | 179 | literals = { 180 | # Constants (constant-face) 181 | nullValue = null; 182 | trueValue = true; 183 | falseValue = false; 184 | builtinsConstant = builtins; 185 | 186 | # Numbers (number-face) 187 | integer = 42; 188 | negativeInt = -10; 189 | float = 3.14159; 190 | negativeFloat = -2.71828; 191 | 192 | # Strings (string-face) 193 | simpleString = "hello world"; 194 | withEscape = "line1\nline2\ttabbed"; 195 | interpolated = "Hello ${name}, you are ${toString age} years old"; 196 | 197 | # Indented strings 198 | multiline = '' 199 | This is a 200 | multiline string 201 | with ${interpolation} 202 | ''; 203 | 204 | # Paths (string-face for paths) 205 | relativePath = ./some/path; 206 | absolutePath = /absolute/path; 207 | homePath = ~/my/home; 208 | storePath = ; 209 | 210 | # URIs (string-face) 211 | httpUrl = "https://example.com/file.tar.gz"; 212 | ftpUrl = "ftp://ftp.example.com/file"; 213 | }; 214 | 215 | # ================================================================= 216 | # SECTION 6: OPERATORS 217 | # ================================================================= 218 | 219 | operators = { 220 | # Arithmetic (operator-face) 221 | arithmetic = 1 + 2 - 3 * 4 / 5; 222 | 223 | # Comparison 224 | comparison = x < y && a > b || c == d; 225 | notEqual = e != f; 226 | 227 | # Logical 228 | logical = !false && (true || false); 229 | 230 | # String/list concatenation 231 | stringConcat = "hello" + " " + "world"; 232 | listConcat = list1 ++ list2; 233 | 234 | # Attribute operations 235 | attrUpdate = oldSet // { 236 | newAttr = "value"; 237 | }; 238 | hasAttr = obj ? attr; 239 | attrAccess = obj.attr or defaultValue; 240 | 241 | # Implication 242 | implies = cond -> result; 243 | 244 | # Unary operators 245 | negation = -42; 246 | boolNot = !true; 247 | 248 | # At pattern 249 | atPattern = args@{}: args; 250 | }; 251 | 252 | # ================================================================= 253 | # SECTION 7: COLLECTIONS 254 | # ================================================================= 255 | 256 | collections = { 257 | # Lists (bracket-face for [], number/string-face for contents) 258 | simpleList = [ 259 | 1 260 | 2 261 | 3 262 | "four" 263 | ]; 264 | 265 | mixedList = [ 266 | 1 267 | 2 268 | 3 269 | "strings" 270 | "in" 271 | "list" 272 | { attr = "value"; } 273 | (x: x + 1) 274 | ]; 275 | 276 | # Attribute sets (bracket-face for {}) 277 | simpleSet = { 278 | a = 1; 279 | b = 2; 280 | }; 281 | 282 | nestedSet = { 283 | level1 = { 284 | level2 = { 285 | level3 = "deep"; 286 | }; 287 | }; 288 | }; 289 | 290 | # Nested attribute paths (property-name-face) 291 | withPath = { 292 | nested.deep.path = "value"; 293 | }; 294 | }; 295 | 296 | # ================================================================= 297 | # SECTION 8: EXPRESSIONS 298 | # ================================================================= 299 | 300 | expressions = { 301 | # apply_expression - function calls 302 | functionCall = func arg; 303 | 304 | # assert_expression 305 | withAssert = 306 | assert x > 0; 307 | x + 1; 308 | 309 | # attrset_expression 310 | simpleSet = { 311 | a = 1; 312 | b = 2; 313 | }; 314 | 315 | # binary_expression 316 | binaryOps = 1 + 2 * 3; 317 | 318 | # if_expression 319 | conditional = if true then "yes" else "no"; 320 | 321 | # let_expression 322 | letExpr = 323 | let 324 | x = 1; 325 | y = 2; 326 | in 327 | x + y; 328 | 329 | # list_expression 330 | list = [ 331 | 1 332 | 2 333 | 3 334 | "four" 335 | ]; 336 | 337 | # parenthesized_expression 338 | grouped = (1 + 2) * 3; 339 | 340 | # rec_attrset_expression 341 | recursive = rec { 342 | x = 1; 343 | y = x + 1; 344 | }; 345 | 346 | # select_expression 347 | propertyAccess = obj.property.nested; 348 | withDefault = obj.prop or defaultValue; 349 | 350 | # unary_expression 351 | negation = -x; 352 | boolNot = !false; 353 | 354 | # variable_expression 355 | variable = someVar; 356 | 357 | # with_expression 358 | withExpr = with pkgs; [ 359 | hello 360 | world 361 | ]; 362 | }; 363 | 364 | # ================================================================= 365 | # SECTION 9: COMPLEX REAL-WORLD EXAMPLE 366 | # ================================================================= 367 | 368 | # Comprehensive example showing multiple features together 369 | complexNixOSModule = 370 | { lib 371 | , pkgs 372 | , config 373 | , ... 374 | }: 375 | let 376 | # Inherit from lib (property-use-face) 377 | inherit (lib) mkOption types mkIf; 378 | 379 | # Inherit from pkgs (property-use-face) 380 | inherit (pkgs) hello; 381 | 382 | # Custom function with formals (parameter highlighting) 383 | myFunc = 384 | { arg1 385 | , arg2 ? "default" 386 | , ... 387 | }@args: 388 | if arg1 > 0 then 389 | map toString (filter (x: x != null) args.list) 390 | else 391 | throw "invalid argument: ${arg2}"; 392 | 393 | # Configuration checking (has_attr) 394 | hasService = config ? services.myapp; 395 | in 396 | rec { 397 | # Option definitions 398 | options = { 399 | enable = mkOption { 400 | type = types.bool; 401 | default = false; 402 | description = "Enable the service"; 403 | }; 404 | 405 | package = mkOption { 406 | type = types.package; 407 | default = hello; 408 | description = "Package to use"; 409 | }; 410 | }; 411 | 412 | # Configuration results 413 | config = mkIf config.enable { 414 | # System packages (with expression + property chains) 415 | environment.systemPackages = with pkgs; [ 416 | hello 417 | world 418 | (myFunc { 419 | arg1 = 42; 420 | list = [ 421 | 1 422 | 2 423 | 3 424 | ]; 425 | }) 426 | ]; 427 | 428 | # Service configuration (deep property chains) 429 | systemd.services.myapp = { 430 | description = "My Application Service"; 431 | wantedBy = [ "multi-user.target" ]; 432 | 433 | serviceConfig = { 434 | ExecStart = "${config.services.myapp.package}/bin/myapp"; 435 | Restart = "always"; 436 | User = "myapp"; 437 | }; 438 | }; 439 | 440 | # Networking (property access with defaults) 441 | networking.firewall.allowedTCPPorts = config.services.myapp.ports or [ 8080 ]; 442 | }; 443 | }; 444 | 445 | # ================================================================= 446 | # SECTION 10: EDGE CASES AND SPECIAL SYNTAX 447 | # ================================================================= 448 | 449 | edgeCases = { 450 | # Nested interpolation 451 | nestedInterpolation = "outer ${toString (x + 1)} inner"; 452 | 453 | # Multiple dollar escapes 454 | multipleEscapes = '' 455 | One: ''${first} 456 | Two: ''${second} 457 | Real: ${actual} 458 | ''; 459 | 460 | # Complex function chains 461 | chainedCalls = map (x: x * 2) (filter (x: x > 0) list); 462 | 463 | # Property access with function call 464 | propertyFunction = (lib.mkIf config.enable value).result; 465 | 466 | # Deep attribute paths in bindings 467 | deepBinding = { 468 | level1.level2.level3.level4 = "deep value"; 469 | }; 470 | 471 | # At pattern with ellipses 472 | atPatternEllipses = args@{ x, y, ... }: args; 473 | 474 | # Has attr with deep path 475 | deepHasAttr = obj ? attr.nested.deep.path; 476 | 477 | # Comparison chaining 478 | chainedComparison = x < y && y < z && z < w; 479 | }; 480 | 481 | # ================================================================= 482 | # All node types and semantic distinctions covered! 483 | # ================================================================= 484 | 485 | coverage = { 486 | grammarCoverage = "100%"; 487 | nodeTypes = 41; 488 | faces = 17; 489 | semanticDistinctions = true; 490 | propertyChainVisibility = "excellent"; 491 | }; 492 | } 493 | -------------------------------------------------------------------------------- /nix-ts-mode.el: -------------------------------------------------------------------------------- 1 | ;;; nix-ts-mode.el --- Major mode for Nix expressions, powered by tree-sitter -*- lexical-binding: t -*- 2 | 3 | ;; Maintainer: Remi Gelinas 4 | ;; Homepage: https://github.com/nix-community/nix-ts-mode 5 | ;; Version: 0.1.5 6 | ;; Keywords: nix languages tree-sitter 7 | ;; Package-Requires: ((emacs "29.1")) 8 | 9 | ;; This program is free software; you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with this program. If not, see . 21 | 22 | ;; This file is NOT part of GNU Emacs. 23 | 24 | ;;; Commentary: 25 | 26 | ;; A major mode for editing Nix expressions, powered by the built-in 27 | ;; tree-sitter support in Emacs 29+. 28 | ;; 29 | ;; Features: 30 | ;; - Complete syntax highlighting for all Nix language constructs 31 | ;; - Semantic highlighting distinctions (variables vs properties, etc.) 32 | ;; - Indentation support 33 | ;; 34 | ;; Requires Emacs 30.1+ for the latest font-lock faces including 35 | ;; font-lock-punctuation-face and other tree-sitter enhancements, however, 36 | ;; it will still provide good coverage on 29.1+. 37 | 38 | ;;; Code: 39 | 40 | (require 'treesit) 41 | 42 | (unless (treesit-available-p) 43 | (error "`nix-ts-mode` requires Emacs to be built with tree-sitter support")) 44 | 45 | (declare-function treesit-parser-create "treesit.c") 46 | (declare-function treesit-node-child-by-field-name "treesit.c") 47 | (declare-function treesit-node-type "treesit.c") 48 | 49 | ;; Other 50 | 51 | (defgroup nix-ts nil 52 | "Major mode for editing Nix expressions." 53 | :prefix "nix-ts-" 54 | :group 'languages) 55 | 56 | (defcustom nix-ts-mode-indent-offset 2 57 | "Number of spaces for each indentation step in `nix-ts-mode'." 58 | :type 'integer 59 | :safe 'integerp) 60 | 61 | (defvar nix-ts--treesit-builtins 62 | ;; nix eval --impure --expr 'with builtins; filter (x: !(elem x [ "abort" "derivation" "import" "throw" ]) && isFunction builtins.${x}) (attrNames builtins)' 63 | ;; Also includes primops (__* functions) 64 | '("add" "addErrorContext" "all" "any" "appendContext" "attrNames" "attrValues" "baseNameOf" 65 | "bitAnd" "bitOr" "bitXor" "break" "catAttrs" "ceil" "compareVersions" "concatLists" "concatMap" 66 | "concatStringsSep" "deepSeq" "derivation" "derivationStrict" "dirOf" "div" "elem" "elemAt" 67 | "fetchGit" "fetchMercurial" "fetchTarball" "fetchTree" "fetchurl" "filter" "filterSource" 68 | "findFile" "floor" "foldl'" "fromJSON" "fromTOML" "functionArgs" "genList" "genericClosure" 69 | "getAttr" "getContext" "getEnv" "getFlake" "groupBy" "hasAttr" "hasContext" "hashFile" 70 | "hashString" "head" "intersectAttrs" "isAttrs" "isBool" "isFloat" "isFunction" "isInt" "isList" 71 | "isNull" "isPath" "isString" "length" "lessThan" "listToAttrs" "map" "mapAttrs" "match" "mul" 72 | "parseDrvName" "partition" "path" "pathExists" "placeholder" "readDir" "readFile" "removeAttrs" 73 | "replaceStrings" "scopedImport" "seq" "sort" "split" "splitVersion" "storePath" "stringLength" 74 | "sub" "substring" "tail" "toFile" "toJSON" "toPath" "toString" "toXML" "trace" "traceVerbose" 75 | "tryEval" "typeOf" "unsafeDiscardOutputDependency" "unsafeDiscardStringContext" 76 | "unsafeGetAttrPos" "zipAttrsWith" 77 | ;; primops (__ in nix repl) 78 | "__add" "__addErrorContext" "__all" "__any" "__appendContext" "__attrNames" "__attrValues" 79 | "__baseNameOf" "__bitAnd" "__bitOr" "__bitXor" "__catAttrs" "__ceil" "__compareVersions" 80 | "__concatLists" "__concatMap" "__concatStringsSep" "__currentSystem" "__currentTime" "__deepSeq" 81 | "__div" "__elem" "__elemAt" "__fetchurl" "__filter" "__filterSource" "__findFile" "__floor" 82 | "__foldl'" "__fromJSON" "__functionArgs" "__genList" "__genericClosure" "__getAttr" 83 | "__getContext" "__getEnv" "__getFlake" "__groupBy" "__hasAttr" "__hasContext" "__hashFile" 84 | "__hashString" "__head" "__intersectAttrs" "__isAttrs" "__isBool" "__isFloat" "__isFunction" 85 | "__isInt" "__isList" "__isPath" "__isString" "__langVersion" "__length" "__lessThan" 86 | "__listToAttrs" "__mapAttrs" "__match" "__mul" "__nixPath" "__nixVersion" "__parseDrvName" 87 | "__partition" "__path" "__pathExists" "__placeholder" "__readDir" "__readFile" "__removeAttrs" 88 | "__replaceStrings" "__seq" "__sort" "__split" "__splitVersion" "__storeDir" "__storePath" 89 | "__stringLength" "__sub" "__substring" "__tail" "__toFile" "__toJSON" "__toPath" "__toString" 90 | "__toXML" "__trace" "__traceVerbose" "__tryEval" "__typeOf" "__unsafeDiscardOutputDependency" 91 | "__unsafeDiscardStringContext" "__unsafeGetAttrPos" "__zipAttrsWith")) 92 | 93 | (defvar nix-ts--treesit-constants 94 | ;; nix eval --impure --expr 'with builtins; filter (x: !(isFunction builtins.${x} || isBool builtins.${x})) (attrNames builtins)' 95 | '("builtins" "currentSystem" "currentTime" "langVersion" "nixPath" "nixVersion" "null" "storeDir")) 96 | 97 | ;; Settings 98 | (defvar nix-ts-mode--font-lock-settings 99 | (treesit-font-lock-rules 100 | :language 'nix 101 | :feature 'bracket 102 | '(["(" ")" "[" "]" "{" "}"] @font-lock-bracket-face) 103 | 104 | :language 'nix 105 | :feature 'comment 106 | '((comment) @font-lock-comment-face) 107 | 108 | :language 'nix 109 | :feature 'delimiter 110 | '(["," "." ";"] @font-lock-delimiter-face) 111 | 112 | :language 'nix 113 | :feature 'keyword 114 | :override t 115 | `((let_expression 116 | (["let" "in"] @font-lock-keyword-face)) 117 | ;; Deprecated let attrset expression (let { body = ...; }) 118 | (let_attrset_expression 119 | ("let" @font-lock-keyword-face)) 120 | (if_expression 121 | ["if" "then" "else"] @font-lock-keyword-face) 122 | (rec_attrset_expression 123 | ("rec" @font-lock-keyword-face)) 124 | (with_expression 125 | ("with" @font-lock-keyword-face)) 126 | (inherit 127 | ("inherit" @font-lock-keyword-face)) 128 | (inherit_from 129 | ("inherit" @font-lock-keyword-face)) 130 | (assert_expression 131 | ("assert" @font-lock-keyword-face)) 132 | ;; Special keywords: throw, abort, import 133 | ((variable_expression name: (identifier) @font-lock-keyword-face) 134 | (:match 135 | ,(rx-to-string 136 | `(seq bol (or "throw" "abort" "import") 137 | eol)) 138 | @font-lock-keyword-face)) 139 | 140 | ;; "or" is technically an operator, but we fontify it as a keyword 141 | (select_expression 142 | ("or" @font-lock-keyword-face))) 143 | 144 | :language 'nix 145 | :feature 'string 146 | :override t 147 | `((string_fragment) @font-lock-string-face 148 | (string_expression 149 | ("\"" @font-lock-string-face)) 150 | (indented_string_expression 151 | ("''" @font-lock-string-face)) 152 | ;; Escape sequences in strings 153 | (escape_sequence) @font-lock-escape-face 154 | ;; Dollar escape in indented strings (''$) 155 | (dollar_escape) @font-lock-escape-face 156 | (interpolation 157 | (["${" "}"] @font-lock-punctuation-face))) 158 | 159 | :language 'nix 160 | :feature 'operator 161 | `((binary_expression operator: _ @font-lock-operator-face) 162 | (unary_expression operator: _ @font-lock-operator-face) 163 | ;; has_attr_expression (x ? y to test if attribute exists) 164 | (has_attr_expression operator: _ @font-lock-operator-face) 165 | ;; @ and ? operators 166 | ["@" "?" "="] @font-lock-operator-face) 167 | 168 | :language 'nix 169 | :feature 'number 170 | `([(integer_expression) (float_expression)] @font-lock-number-face) 171 | 172 | :language 'nix 173 | :feature 'path 174 | `(;; Regular paths 175 | (path_expression) @font-lock-constant-face 176 | ;; Home paths (~/) 177 | (hpath_expression) @font-lock-constant-face 178 | ;; Store paths () 179 | (spath_expression) @font-lock-constant-face) 180 | 181 | :language 'nix 182 | :feature 'uri 183 | `((uri_expression) @font-lock-string-face) 184 | 185 | :language 'nix 186 | :feature 'parameter 187 | `(;; Function parameters in formals ({ x, y, ... }) 188 | (formal name: (identifier) @font-lock-variable-name-face) 189 | ;; Universal function parameter (x: body) - simple case without destructuring 190 | (function_expression 191 | universal: (identifier) @font-lock-variable-name-face) 192 | ;; @ pattern operator 193 | (function_expression 194 | "@" @font-lock-operator-face)) 195 | 196 | ;; At-pattern binding needs override to take precedence over universal parameter 197 | :language 'nix 198 | :feature 'parameter-atpattern 199 | :override t 200 | `(;; @ pattern identifier - forward order: args@{ x, y } 201 | ;; Use type-face for visual distinction - it's the "base object" containing all params 202 | (function_expression 203 | universal: (identifier) @font-lock-type-face 204 | "@") 205 | ;; @ pattern identifier - reverse order: { x, y }@opts 206 | (function_expression 207 | "@" 208 | universal: (identifier) @font-lock-type-face)) 209 | 210 | :language 'nix 211 | :feature 'function-call 212 | `(;; Function calls: highlight function name in apply_expression 213 | (apply_expression 214 | function: (variable_expression 215 | name: (identifier) @font-lock-function-call-face)) 216 | ;; Function calls with select_expression (builtins.map, lib.mkOption, etc.) 217 | (apply_expression 218 | function: (select_expression 219 | attrpath: (attrpath 220 | (identifier) @font-lock-function-call-face :anchor)))) 221 | 222 | :language 'nix 223 | :feature 'property 224 | `(;; Base variable in select expressions - use type-face for visual distinction 225 | ;; (Nix has no type system, so this face is available and visually distinct) 226 | (select_expression 227 | expression: (variable_expression 228 | name: (identifier) @font-lock-type-face)) 229 | ;; Property access in select expressions - the MEMBERS (obj.property) 230 | (select_expression 231 | attrpath: (attrpath 232 | (identifier) @font-lock-property-use-face)) 233 | ;; Binding attributes in attrsets (all identifiers in the path) 234 | (binding_set 235 | (binding 236 | attrpath: (attrpath 237 | (identifier) @font-lock-property-name-face))) 238 | ;; Source in inherit_from - use type-face for visual distinction (inherit (SOURCE) attrs) 239 | (inherit_from 240 | (variable_expression 241 | name: (identifier) @font-lock-type-face)) 242 | ;; Inherited attributes FROM a source (inherit (pkgs) hello) - these are properties 243 | (inherit_from 244 | attrs: (inherited_attrs 245 | (identifier) @font-lock-property-use-face))) 246 | 247 | :language 'nix 248 | :feature 'builtin 249 | :override t 250 | `(;; Builtin functions (standalone) 251 | (variable_expression name: (identifier) @font-lock-builtin-face 252 | (:match 253 | ,(rx-to-string 254 | `(seq bol (or ,@nix-ts--treesit-builtins) 255 | eol)) 256 | @font-lock-builtin-face)) 257 | ;; Builtin functions with builtins prefix (builtins.map, etc.) 258 | (select_expression 259 | expression: (variable_expression 260 | name: (identifier) @_builtins 261 | (:equal @_builtins "builtins")) 262 | attrpath: (attrpath 263 | attr: (identifier) @font-lock-builtin-face))) 264 | 265 | :language 'nix 266 | :feature 'constant 267 | :override t 268 | `(;; Language constants (boolean) 269 | (variable_expression name: (identifier) @font-lock-constant-face 270 | (:match 271 | ,(rx-to-string 272 | `(seq bol (or ,@nix-ts--treesit-constants "true" "false") 273 | eol)) 274 | @font-lock-constant-face))) 275 | 276 | :language 'nix 277 | :feature 'definition 278 | :override t 279 | `(;; Function definitions (name = x: y;) 280 | (binding 281 | attrpath: (attrpath 282 | (identifier) @font-lock-function-name-face :anchor) 283 | expression: (function_expression))) 284 | 285 | :language 'nix 286 | :feature 'variable 287 | `(;; General variable references 288 | (variable_expression 289 | name: (identifier) @font-lock-variable-use-face) 290 | ;; Plain inherit (inherit x y z) - these create NEW variable bindings 291 | (inherit 292 | attrs: (inherited_attrs 293 | (identifier) @font-lock-variable-name-face))) 294 | 295 | :language 'nix 296 | :feature 'paren-base 297 | :override t 298 | `(;; Parenthesized base in select - highlight parens with type-face for visual grouping 299 | ;; This overrides bracket-face to make the base stand out like simple variable bases 300 | (select_expression 301 | expression: (parenthesized_expression 302 | ["(" ")"] @font-lock-type-face))) 303 | 304 | :language 'nix 305 | :feature 'ellipses 306 | `((ellipses) @font-lock-punctuation-face) 307 | 308 | :language 'nix 309 | :feature 'punctuation 310 | `((function_expression 311 | ":" @font-lock-punctuation-face)) 312 | 313 | :language 'nix 314 | :feature 'error 315 | :override t 316 | '((ERROR) @font-lock-warning-face)) 317 | "Tree-sitter font-lock settings for `nix-ts-mode'.") 318 | 319 | ;; Indentation 320 | (defun nix-ts-indent-multiline-string (n parent bol &rest rest) 321 | "Return the indent prefix for the current multi-line string line. 322 | For the first line, this is the previous line offset+nix-indent-offset, 323 | and for subsequent lines it's the previous line's indentation." 324 | ;; If the current line is the first relevant one in the multiline 325 | ;; string, indent it to the default level (2 spaces past the 326 | ;; previous line's offset): 327 | (if (and (equal (treesit-node-child (treesit-node-parent parent) 1) 328 | parent) 329 | (<= (count-lines (treesit-node-start parent) (point)) 1)) 330 | (+ (apply (alist-get 'parent-bol treesit-simple-indent-presets) 331 | n parent bol rest) 332 | nix-ts-mode-indent-offset) 333 | ;; If the current line is already indented to some level, leave it alone: 334 | (if (/= bol 335 | (save-excursion 336 | (beginning-of-line) 337 | (point))) 338 | bol 339 | ;; otherwise, indent to the level of the previous line by default. 340 | (save-excursion 341 | (forward-line -1) 342 | (if (looking-at "\s+") 343 | (match-end 0) 344 | ;; in case neither line has discernable indentation, just 345 | ;; indent to bol 346 | bol))))) 347 | 348 | (defvar nix-ts-mode-indent-rules 349 | `((nix 350 | ((parent-is "source_code") column-0 0) 351 | ((node-is "]") parent-bol 0) 352 | ((node-is ")") parent-bol 0) 353 | ((node-is "}") parent-bol 0) 354 | ((node-is "then") parent-bol 0) 355 | ((node-is "else") parent-bol 0) 356 | ((node-is ";") parent-bol 0) 357 | ((node-is "^in$") parent-bol 0) 358 | ((node-is "binding_set") parent-bol nix-ts-mode-indent-offset) 359 | ((match "interpolation" "indented_string_expression" nil nil nil) nix-ts-indent-multiline-string 0) 360 | ((parent-is "indented_string_expression") parent-bol 0) 361 | ((parent-is "string_fragment") nix-ts-indent-multiline-string 0) 362 | ((parent-is "binding_set") parent-bol 0) 363 | ((parent-is "binding") parent-bol nix-ts-mode-indent-offset) 364 | ((parent-is "let_expression") parent-bol nix-ts-mode-indent-offset) 365 | ((parent-is "attrset_expression") parent-bol nix-ts-mode-indent-offset) 366 | ((node-is "inherited_attrs") parent-bol nix-ts-mode-indent-offset) 367 | ((parent-is "inherited_attrs") parent-bol 0) 368 | ((parent-is "list_expression") parent-bol nix-ts-mode-indent-offset) 369 | ((parent-is "apply_expression") parent-bol nix-ts-mode-indent-offset) 370 | ((parent-is "parenthesized_expression") parent-bol nix-ts-mode-indent-offset) 371 | ((parent-is "formals") parent-bol nix-ts-mode-indent-offset))) 372 | "Tree-sitter indent rules for `nix-ts-mode'.") 373 | 374 | ;; Keymap 375 | (defvar nix-ts-mode-map 376 | (let ((map (make-sparse-keymap))) 377 | map) 378 | "Keymap for `nix-ts-mode'.") 379 | 380 | ;; Syntax map 381 | (defvar nix-ts-mode--syntax-table 382 | (let ((table (make-syntax-table))) 383 | (modify-syntax-entry ?# "< b" table) 384 | (modify-syntax-entry ?\n "> b" table) 385 | (modify-syntax-entry ?/ ". 14" table) 386 | (modify-syntax-entry ?* ". 23" table) 387 | table) 388 | "Syntax table for `nix-ts-mode'.") 389 | 390 | (defun nix-ts-mode--defun-name (node) 391 | "Return the defun name of NODE. 392 | Return nil if there is no name or if NODE is not a defun node." 393 | (pcase (treesit-node-type node) 394 | ("binding" 395 | (treesit-node-text 396 | (treesit-node-child-by-field-name node "attrpath") t)))) 397 | 398 | ;;;###autoload 399 | (define-derived-mode nix-ts-mode prog-mode "Nix" 400 | "Major mode for editing Nix expressions, powered by treesitter. 401 | 402 | \\{nix-ts-mode-map}" 403 | :syntax-table nix-ts-mode--syntax-table 404 | 405 | (when (treesit-ready-p 'nix) 406 | (treesit-parser-create 'nix) 407 | 408 | ;; Font locking 409 | (setq-local treesit-font-lock-settings nix-ts-mode--font-lock-settings) 410 | 411 | (setq-local treesit-font-lock-feature-list 412 | '((comment builtin constant) 413 | (string path uri) 414 | (number operator definition function-call keyword) 415 | (parameter property variable bracket delimiter ellipses punctuation paren-base parameter-atpattern error))) 416 | 417 | ;; Comments 418 | (setq-local comment-start "# ") 419 | (setq-local comment-start-skip "#+\\s-*") 420 | 421 | ;; Indentation 422 | (setq-local treesit-simple-indent-rules nix-ts-mode-indent-rules) 423 | 424 | ;; Imenu. 425 | (setq-local treesit-simple-imenu-settings 426 | `((nil "\\`binding\\'" nil nil))) 427 | 428 | ;; Navigation. 429 | (setq-local treesit-defun-type-regexp (rx (or "binding"))) 430 | (setq-local treesit-defun-name-function #'nix-ts-mode--defun-name) 431 | 432 | (treesit-major-mode-setup)) 433 | 434 | (when (functionp 'derived-mode-add-parents) 435 | (derived-mode-add-parents 'nix-ts-mode '(nix-mode)))) 436 | 437 | (provide 'nix-ts-mode) 438 | ;;; nix-ts-mode.el ends here 439 | 440 | ;; Local Variables: 441 | ;; indent-tabs-mode: nil 442 | ;; End: 443 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright © 2007 Free Software Foundation, Inc. 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The GNU General Public License is a free, copyleft license for software and 13 | other kinds of works. 14 | 15 | The licenses for most software and other practical works are designed to take 16 | away your freedom to share and change the works. By contrast, the GNU General 17 | Public License is intended to guarantee your freedom to share and change all 18 | versions of a program--to make sure it remains free software for all its users. 19 | We, the Free Software Foundation, use the GNU General Public License for most 20 | of our software; it applies also to any other work released this way by its 21 | authors. You can apply it to your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our 24 | General Public Licenses are designed to make sure that you have the freedom 25 | to distribute copies of free software (and charge for them if you wish), that 26 | you receive source code or can get it if you want it, that you can change 27 | the software or use pieces of it in new free programs, and that you know you 28 | can do these things. 29 | 30 | To protect your rights, we need to prevent others from denying you these rights 31 | or asking you to surrender the rights. Therefore, you have certain responsibilities 32 | if you distribute copies of the software, or if you modify it: responsibilities 33 | to respect the freedom of others. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or 36 | for a fee, you must pass on to the recipients the same freedoms that you received. 37 | You must make sure that they, too, receive or can get the source code. And 38 | you must show them these terms so they know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: (1) assert 41 | copyright on the software, and (2) offer you this License giving you legal 42 | permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains that 45 | there is no warranty for this free software. For both users' and authors' 46 | sake, the GPL requires that modified versions be marked as changed, so that 47 | their problems will not be attributed erroneously to authors of previous versions. 48 | 49 | Some devices are designed to deny users access to install or run modified 50 | versions of the software inside them, although the manufacturer can do so. 51 | This is fundamentally incompatible with the aim of protecting users' freedom 52 | to change the software. The systematic pattern of such abuse occurs in the 53 | area of products for individuals to use, which is precisely where it is most 54 | unacceptable. Therefore, we have designed this version of the GPL to prohibit 55 | the practice for those products. If such problems arise substantially in other 56 | domains, we stand ready to extend this provision to those domains in future 57 | versions of the GPL, as needed to protect the freedom of users. 58 | 59 | Finally, every program is threatened constantly by software patents. States 60 | should not allow patents to restrict development and use of software on general-purpose 61 | computers, but in those that do, we wish to avoid the special danger that 62 | patents applied to a free program could make it effectively proprietary. To 63 | prevent this, the GPL assures that patents cannot be used to render the program 64 | non-free. 65 | 66 | The precise terms and conditions for copying, distribution and modification 67 | follow. 68 | 69 | TERMS AND CONDITIONS 70 | 71 | 0. Definitions. 72 | 73 | "This License" refers to version 3 of the GNU General Public License. 74 | 75 | "Copyright" also means copyright-like laws that apply to other kinds of works, 76 | such as semiconductor masks. 77 | 78 | "The Program" refers to any copyrightable work licensed under this License. 79 | Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals 80 | or organizations. 81 | 82 | To "modify" a work means to copy from or adapt all or part of the work in 83 | a fashion requiring copyright permission, other than the making of an exact 84 | copy. The resulting work is called a "modified version" of the earlier work 85 | or a work "based on" the earlier work. 86 | 87 | A "covered work" means either the unmodified Program or a work based on the 88 | Program. 89 | 90 | To "propagate" a work means to do anything with it that, without permission, 91 | would make you directly or secondarily liable for infringement under applicable 92 | copyright law, except executing it on a computer or modifying a private copy. 93 | Propagation includes copying, distribution (with or without modification), 94 | making available to the public, and in some countries other activities as 95 | well. 96 | 97 | To "convey" a work means any kind of propagation that enables other parties 98 | to make or receive copies. Mere interaction with a user through a computer 99 | network, with no transfer of a copy, is not conveying. 100 | 101 | An interactive user interface displays "Appropriate Legal Notices" to the 102 | extent that it includes a convenient and prominently visible feature that 103 | (1) displays an appropriate copyright notice, and (2) tells the user that 104 | there is no warranty for the work (except to the extent that warranties are 105 | provided), that licensees may convey the work under this License, and how 106 | to view a copy of this License. If the interface presents a list of user commands 107 | or options, such as a menu, a prominent item in the list meets this criterion. 108 | 109 | 1. Source Code. 110 | 111 | The "source code" for a work means the preferred form of the work for making 112 | modifications to it. "Object code" means any non-source form of a work. 113 | 114 | A "Standard Interface" means an interface that either is an official standard 115 | defined by a recognized standards body, or, in the case of interfaces specified 116 | for a particular programming language, one that is widely used among developers 117 | working in that language. 118 | 119 | The "System Libraries" of an executable work include anything, other than 120 | the work as a whole, that (a) is included in the normal form of packaging 121 | a Major Component, but which is not part of that Major Component, and (b) 122 | serves only to enable use of the work with that Major Component, or to implement 123 | a Standard Interface for which an implementation is available to the public 124 | in source code form. A "Major Component", in this context, means a major essential 125 | component (kernel, window system, and so on) of the specific operating system 126 | (if any) on which the executable work runs, or a compiler used to produce 127 | the work, or an object code interpreter used to run it. 128 | 129 | The "Corresponding Source" for a work in object code form means all the source 130 | code needed to generate, install, and (for an executable work) run the object 131 | code and to modify the work, including scripts to control those activities. 132 | However, it does not include the work's System Libraries, or general-purpose 133 | tools or generally available free programs which are used unmodified in performing 134 | those activities but which are not part of the work. For example, Corresponding 135 | Source includes interface definition files associated with source files for 136 | the work, and the source code for shared libraries and dynamically linked 137 | subprograms that the work is specifically designed to require, such as by 138 | intimate data communication or control flow between those subprograms and 139 | other parts of the work. 140 | 141 | The Corresponding Source need not include anything that users can regenerate 142 | automatically from other parts of the Corresponding Source. 143 | 144 | The Corresponding Source for a work in source code form is that same work. 145 | 146 | 2. Basic Permissions. 147 | 148 | All rights granted under this License are granted for the term of copyright 149 | on the Program, and are irrevocable provided the stated conditions are met. 150 | This License explicitly affirms your unlimited permission to run the unmodified 151 | Program. The output from running a covered work is covered by this License 152 | only if the output, given its content, constitutes a covered work. This License 153 | acknowledges your rights of fair use or other equivalent, as provided by copyright 154 | law. 155 | 156 | You may make, run and propagate covered works that you do not convey, without 157 | conditions so long as your license otherwise remains in force. You may convey 158 | covered works to others for the sole purpose of having them make modifications 159 | exclusively for you, or provide you with facilities for running those works, 160 | provided that you comply with the terms of this License in conveying all material 161 | for which you do not control copyright. Those thus making or running the covered 162 | works for you must do so exclusively on your behalf, under your direction 163 | and control, on terms that prohibit them from making any copies of your copyrighted 164 | material outside their relationship with you. 165 | 166 | Conveying under any other circumstances is permitted solely under the conditions 167 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 168 | 169 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 170 | 171 | No covered work shall be deemed part of an effective technological measure 172 | under any applicable law fulfilling obligations under article 11 of the WIPO 173 | copyright treaty adopted on 20 December 1996, or similar laws prohibiting 174 | or restricting circumvention of such measures. 175 | 176 | When you convey a covered work, you waive any legal power to forbid circumvention 177 | of technological measures to the extent such circumvention is effected by 178 | exercising rights under this License with respect to the covered work, and 179 | you disclaim any intention to limit operation or modification of the work 180 | as a means of enforcing, against the work's users, your or third parties' 181 | legal rights to forbid circumvention of technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you receive 186 | it, in any medium, provided that you conspicuously and appropriately publish 187 | on each copy an appropriate copyright notice; keep intact all notices stating 188 | that this License and any non-permissive terms added in accord with section 189 | 7 apply to the code; keep intact all notices of the absence of any warranty; 190 | and give all recipients a copy of this License along with the Program. 191 | 192 | You may charge any price or no price for each copy that you convey, and you 193 | may offer support or warranty protection for a fee. 194 | 195 | 5. Conveying Modified Source Versions. 196 | 197 | You may convey a work based on the Program, or the modifications to produce 198 | it from the Program, in the form of source code under the terms of section 199 | 4, provided that you also meet all of these conditions: 200 | 201 | a) The work must carry prominent notices stating that you modified it, and 202 | giving a relevant date. 203 | 204 | b) The work must carry prominent notices stating that it is released under 205 | this License and any conditions added under section 7. This requirement modifies 206 | the requirement in section 4 to "keep intact all notices". 207 | 208 | c) You must license the entire work, as a whole, under this License to anyone 209 | who comes into possession of a copy. This License will therefore apply, along 210 | with any applicable section 7 additional terms, to the whole of the work, 211 | and all its parts, regardless of how they are packaged. This License gives 212 | no permission to license the work in any other way, but it does not invalidate 213 | such permission if you have separately received it. 214 | 215 | d) If the work has interactive user interfaces, each must display Appropriate 216 | Legal Notices; however, if the Program has interactive interfaces that do 217 | not display Appropriate Legal Notices, your work need not make them do so. 218 | 219 | A compilation of a covered work with other separate and independent works, 220 | which are not by their nature extensions of the covered work, and which are 221 | not combined with it such as to form a larger program, in or on a volume of 222 | a storage or distribution medium, is called an "aggregate" if the compilation 223 | and its resulting copyright are not used to limit the access or legal rights 224 | of the compilation's users beyond what the individual works permit. Inclusion 225 | of a covered work in an aggregate does not cause this License to apply to 226 | the other parts of the aggregate. 227 | 228 | 6. Conveying Non-Source Forms. 229 | 230 | You may convey a covered work in object code form under the terms of sections 231 | 4 and 5, provided that you also convey the machine-readable Corresponding 232 | Source under the terms of this License, in one of these ways: 233 | 234 | a) Convey the object code in, or embodied in, a physical product (including 235 | a physical distribution medium), accompanied by the Corresponding Source fixed 236 | on a durable physical medium customarily used for software interchange. 237 | 238 | b) Convey the object code in, or embodied in, a physical product (including 239 | a physical distribution medium), accompanied by a written offer, valid for 240 | at least three years and valid for as long as you offer spare parts or customer 241 | support for that product model, to give anyone who possesses the object code 242 | either (1) a copy of the Corresponding Source for all the software in the 243 | product that is covered by this License, on a durable physical medium customarily 244 | used for software interchange, for a price no more than your reasonable cost 245 | of physically performing this conveying of source, or (2) access to copy the 246 | Corresponding Source from a network server at no charge. 247 | 248 | c) Convey individual copies of the object code with a copy of the written 249 | offer to provide the Corresponding Source. This alternative is allowed only 250 | occasionally and noncommercially, and only if you received the object code 251 | with such an offer, in accord with subsection 6b. 252 | 253 | d) Convey the object code by offering access from a designated place (gratis 254 | or for a charge), and offer equivalent access to the Corresponding Source 255 | in the same way through the same place at no further charge. You need not 256 | require recipients to copy the Corresponding Source along with the object 257 | code. If the place to copy the object code is a network server, the Corresponding 258 | Source may be on a different server (operated by you or a third party) that 259 | supports equivalent copying facilities, provided you maintain clear directions 260 | next to the object code saying where to find the Corresponding Source. Regardless 261 | of what server hosts the Corresponding Source, you remain obligated to ensure 262 | that it is available for as long as needed to satisfy these requirements. 263 | 264 | e) Convey the object code using peer-to-peer transmission, provided you inform 265 | other peers where the object code and Corresponding Source of the work are 266 | being offered to the general public at no charge under subsection 6d. 267 | 268 | A separable portion of the object code, whose source code is excluded from 269 | the Corresponding Source as a System Library, need not be included in conveying 270 | the object code work. 271 | 272 | A "User Product" is either (1) a "consumer product", which means any tangible 273 | personal property which is normally used for personal, family, or household 274 | purposes, or (2) anything designed or sold for incorporation into a dwelling. 275 | In determining whether a product is a consumer product, doubtful cases shall 276 | be resolved in favor of coverage. For a particular product received by a particular 277 | user, "normally used" refers to a typical or common use of that class of product, 278 | regardless of the status of the particular user or of the way in which the 279 | particular user actually uses, or expects or is expected to use, the product. 280 | A product is a consumer product regardless of whether the product has substantial 281 | commercial, industrial or non-consumer uses, unless such uses represent the 282 | only significant mode of use of the product. 283 | 284 | "Installation Information" for a User Product means any methods, procedures, 285 | authorization keys, or other information required to install and execute modified 286 | versions of a covered work in that User Product from a modified version of 287 | its Corresponding Source. The information must suffice to ensure that the 288 | continued functioning of the modified object code is in no case prevented 289 | or interfered with solely because modification has been made. 290 | 291 | If you convey an object code work under this section in, or with, or specifically 292 | for use in, a User Product, and the conveying occurs as part of a transaction 293 | in which the right of possession and use of the User Product is transferred 294 | to the recipient in perpetuity or for a fixed term (regardless of how the 295 | transaction is characterized), the Corresponding Source conveyed under this 296 | section must be accompanied by the Installation Information. But this requirement 297 | does not apply if neither you nor any third party retains the ability to install 298 | modified object code on the User Product (for example, the work has been installed 299 | in ROM). 300 | 301 | The requirement to provide Installation Information does not include a requirement 302 | to continue to provide support service, warranty, or updates for a work that 303 | has been modified or installed by the recipient, or for the User Product in 304 | which it has been modified or installed. Access to a network may be denied 305 | when the modification itself materially and adversely affects the operation 306 | of the network or violates the rules and protocols for communication across 307 | the network. 308 | 309 | Corresponding Source conveyed, and Installation Information provided, in accord 310 | with this section must be in a format that is publicly documented (and with 311 | an implementation available to the public in source code form), and must require 312 | no special password or key for unpacking, reading or copying. 313 | 314 | 7. Additional Terms. 315 | 316 | "Additional permissions" are terms that supplement the terms of this License 317 | by making exceptions from one or more of its conditions. Additional permissions 318 | that are applicable to the entire Program shall be treated as though they 319 | were included in this License, to the extent that they are valid under applicable 320 | law. If additional permissions apply only to part of the Program, that part 321 | may be used separately under those permissions, but the entire Program remains 322 | governed by this License without regard to the additional permissions. 323 | 324 | When you convey a copy of a covered work, you may at your option remove any 325 | additional permissions from that copy, or from any part of it. (Additional 326 | permissions may be written to require their own removal in certain cases when 327 | you modify the work.) You may place additional permissions on material, added 328 | by you to a covered work, for which you have or can give appropriate copyright 329 | permission. 330 | 331 | Notwithstanding any other provision of this License, for material you add 332 | to a covered work, you may (if authorized by the copyright holders of that 333 | material) supplement the terms of this License with terms: 334 | 335 | a) Disclaiming warranty or limiting liability differently from the terms of 336 | sections 15 and 16 of this License; or 337 | 338 | b) Requiring preservation of specified reasonable legal notices or author 339 | attributions in that material or in the Appropriate Legal Notices displayed 340 | by works containing it; or 341 | 342 | c) Prohibiting misrepresentation of the origin of that material, or requiring 343 | that modified versions of such material be marked in reasonable ways as different 344 | from the original version; or 345 | 346 | d) Limiting the use for publicity purposes of names of licensors or authors 347 | of the material; or 348 | 349 | e) Declining to grant rights under trademark law for use of some trade names, 350 | trademarks, or service marks; or 351 | 352 | f) Requiring indemnification of licensors and authors of that material by 353 | anyone who conveys the material (or modified versions of it) with contractual 354 | assumptions of liability to the recipient, for any liability that these contractual 355 | assumptions directly impose on those licensors and authors. 356 | 357 | All other non-permissive additional terms are considered "further restrictions" 358 | within the meaning of section 10. If the Program as you received it, or any 359 | part of it, contains a notice stating that it is governed by this License 360 | along with a term that is a further restriction, you may remove that term. 361 | If a license document contains a further restriction but permits relicensing 362 | or conveying under this License, you may add to a covered work material governed 363 | by the terms of that license document, provided that the further restriction 364 | does not survive such relicensing or conveying. 365 | 366 | If you add terms to a covered work in accord with this section, you must place, 367 | in the relevant source files, a statement of the additional terms that apply 368 | to those files, or a notice indicating where to find the applicable terms. 369 | 370 | Additional terms, permissive or non-permissive, may be stated in the form 371 | of a separately written license, or stated as exceptions; the above requirements 372 | apply either way. 373 | 374 | 8. Termination. 375 | 376 | You may not propagate or modify a covered work except as expressly provided 377 | under this License. Any attempt otherwise to propagate or modify it is void, 378 | and will automatically terminate your rights under this License (including 379 | any patent licenses granted under the third paragraph of section 11). 380 | 381 | However, if you cease all violation of this License, then your license from 382 | a particular copyright holder is reinstated (a) provisionally, unless and 383 | until the copyright holder explicitly and finally terminates your license, 384 | and (b) permanently, if the copyright holder fails to notify you of the violation 385 | by some reasonable means prior to 60 days after the cessation. 386 | 387 | Moreover, your license from a particular copyright holder is reinstated permanently 388 | if the copyright holder notifies you of the violation by some reasonable means, 389 | this is the first time you have received notice of violation of this License 390 | (for any work) from that copyright holder, and you cure the violation prior 391 | to 30 days after your receipt of the notice. 392 | 393 | Termination of your rights under this section does not terminate the licenses 394 | of parties who have received copies or rights from you under this License. 395 | If your rights have been terminated and not permanently reinstated, you do 396 | not qualify to receive new licenses for the same material under section 10. 397 | 398 | 9. Acceptance Not Required for Having Copies. 399 | 400 | You are not required to accept this License in order to receive or run a copy 401 | of the Program. Ancillary propagation of a covered work occurring solely as 402 | a consequence of using peer-to-peer transmission to receive a copy likewise 403 | does not require acceptance. However, nothing other than this License grants 404 | you permission to propagate or modify any covered work. These actions infringe 405 | copyright if you do not accept this License. Therefore, by modifying or propagating 406 | a covered work, you indicate your acceptance of this License to do so. 407 | 408 | 10. Automatic Licensing of Downstream Recipients. 409 | 410 | Each time you convey a covered work, the recipient automatically receives 411 | a license from the original licensors, to run, modify and propagate that work, 412 | subject to this License. You are not responsible for enforcing compliance 413 | by third parties with this License. 414 | 415 | An "entity transaction" is a transaction transferring control of an organization, 416 | or substantially all assets of one, or subdividing an organization, or merging 417 | organizations. If propagation of a covered work results from an entity transaction, 418 | each party to that transaction who receives a copy of the work also receives 419 | whatever licenses to the work the party's predecessor in interest had or could 420 | give under the previous paragraph, plus a right to possession of the Corresponding 421 | Source of the work from the predecessor in interest, if the predecessor has 422 | it or can get it with reasonable efforts. 423 | 424 | You may not impose any further restrictions on the exercise of the rights 425 | granted or affirmed under this License. For example, you may not impose a 426 | license fee, royalty, or other charge for exercise of rights granted under 427 | this License, and you may not initiate litigation (including a cross-claim 428 | or counterclaim in a lawsuit) alleging that any patent claim is infringed 429 | by making, using, selling, offering for sale, or importing the Program or 430 | any portion of it. 431 | 432 | 11. Patents. 433 | 434 | A "contributor" is a copyright holder who authorizes use under this License 435 | of the Program or a work on which the Program is based. The work thus licensed 436 | is called the contributor's "contributor version". 437 | 438 | A contributor's "essential patent claims" are all patent claims owned or controlled 439 | by the contributor, whether already acquired or hereafter acquired, that would 440 | be infringed by some manner, permitted by this License, of making, using, 441 | or selling its contributor version, but do not include claims that would be 442 | infringed only as a consequence of further modification of the contributor 443 | version. For purposes of this definition, "control" includes the right to 444 | grant patent sublicenses in a manner consistent with the requirements of this 445 | License. 446 | 447 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent 448 | license under the contributor's essential patent claims, to make, use, sell, 449 | offer for sale, import and otherwise run, modify and propagate the contents 450 | of its contributor version. 451 | 452 | In the following three paragraphs, a "patent license" is any express agreement 453 | or commitment, however denominated, not to enforce a patent (such as an express 454 | permission to practice a patent or covenant not to sue for patent infringement). 455 | To "grant" such a patent license to a party means to make such an agreement 456 | or commitment not to enforce a patent against the party. 457 | 458 | If you convey a covered work, knowingly relying on a patent license, and the 459 | Corresponding Source of the work is not available for anyone to copy, free 460 | of charge and under the terms of this License, through a publicly available 461 | network server or other readily accessible means, then you must either (1) 462 | cause the Corresponding Source to be so available, or (2) arrange to deprive 463 | yourself of the benefit of the patent license for this particular work, or 464 | (3) arrange, in a manner consistent with the requirements of this License, 465 | to extend the patent license to downstream recipients. "Knowingly relying" 466 | means you have actual knowledge that, but for the patent license, your conveying 467 | the covered work in a country, or your recipient's use of the covered work 468 | in a country, would infringe one or more identifiable patents in that country 469 | that you have reason to believe are valid. 470 | 471 | If, pursuant to or in connection with a single transaction or arrangement, 472 | you convey, or propagate by procuring conveyance of, a covered work, and grant 473 | a patent license to some of the parties receiving the covered work authorizing 474 | them to use, propagate, modify or convey a specific copy of the covered work, 475 | then the patent license you grant is automatically extended to all recipients 476 | of the covered work and works based on it. 477 | 478 | A patent license is "discriminatory" if it does not include within the scope 479 | of its coverage, prohibits the exercise of, or is conditioned on the non-exercise 480 | of one or more of the rights that are specifically granted under this License. 481 | You may not convey a covered work if you are a party to an arrangement with 482 | a third party that is in the business of distributing software, under which 483 | you make payment to the third party based on the extent of your activity of 484 | conveying the work, and under which the third party grants, to any of the 485 | parties who would receive the covered work from you, a discriminatory patent 486 | license (a) in connection with copies of the covered work conveyed by you 487 | (or copies made from those copies), or (b) primarily for and in connection 488 | with specific products or compilations that contain the covered work, unless 489 | you entered into that arrangement, or that patent license was granted, prior 490 | to 28 March 2007. 491 | 492 | Nothing in this License shall be construed as excluding or limiting any implied 493 | license or other defenses to infringement that may otherwise be available 494 | to you under applicable patent law. 495 | 496 | 12. No Surrender of Others' Freedom. 497 | 498 | If conditions are imposed on you (whether by court order, agreement or otherwise) 499 | that contradict the conditions of this License, they do not excuse you from 500 | the conditions of this License. If you cannot convey a covered work so as 501 | to satisfy simultaneously your obligations under this License and any other 502 | pertinent obligations, then as a consequence you may not convey it at all. 503 | For example, if you agree to terms that obligate you to collect a royalty 504 | for further conveying from those to whom you convey the Program, the only 505 | way you could satisfy both those terms and this License would be to refrain 506 | entirely from conveying the Program. 507 | 508 | 13. Use with the GNU Affero General Public License. 509 | 510 | Notwithstanding any other provision of this License, you have permission to 511 | link or combine any covered work with a work licensed under version 3 of the 512 | GNU Affero General Public License into a single combined work, and to convey 513 | the resulting work. The terms of this License will continue to apply to the 514 | part which is the covered work, but the special requirements of the GNU Affero 515 | General Public License, section 13, concerning interaction through a network 516 | will apply to the combination as such. 517 | 518 | 14. Revised Versions of this License. 519 | 520 | The Free Software Foundation may publish revised and/or new versions of the 521 | GNU General Public License from time to time. Such new versions will be similar 522 | in spirit to the present version, but may differ in detail to address new 523 | problems or concerns. 524 | 525 | Each version is given a distinguishing version number. If the Program specifies 526 | that a certain numbered version of the GNU General Public License "or any 527 | later version" applies to it, you have the option of following the terms and 528 | conditions either of that numbered version or of any later version published 529 | by the Free Software Foundation. If the Program does not specify a version 530 | number of the GNU General Public License, you may choose any version ever 531 | published by the Free Software Foundation. 532 | 533 | If the Program specifies that a proxy can decide which future versions of 534 | the GNU General Public License can be used, that proxy's public statement 535 | of acceptance of a version permanently authorizes you to choose that version 536 | for the Program. 537 | 538 | Later license versions may give you additional or different permissions. However, 539 | no additional obligations are imposed on any author or copyright holder as 540 | a result of your choosing to follow a later version. 541 | 542 | 15. Disclaimer of Warranty. 543 | 544 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE 545 | LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 546 | OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 547 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 548 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 549 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM 550 | PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR 551 | CORRECTION. 552 | 553 | 16. Limitation of Liability. 554 | 555 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL 556 | ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM 557 | AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 558 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO 559 | USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED 560 | INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE 561 | PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER 562 | PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 563 | 564 | 17. Interpretation of Sections 15 and 16. 565 | 566 | If the disclaimer of warranty and limitation of liability provided above cannot 567 | be given local legal effect according to their terms, reviewing courts shall 568 | apply local law that most closely approximates an absolute waiver of all civil 569 | liability in connection with the Program, unless a warranty or assumption 570 | of liability accompanies a copy of the Program in return for a fee. END OF 571 | TERMS AND CONDITIONS 572 | 573 | How to Apply These Terms to Your New Programs 574 | 575 | If you develop a new program, and you want it to be of the greatest possible 576 | use to the public, the best way to achieve this is to make it free software 577 | which everyone can redistribute and change under these terms. 578 | 579 | To do so, attach the following notices to the program. It is safest to attach 580 | them to the start of each source file to most effectively state the exclusion 581 | of warranty; and each file should have at least the "copyright" line and a 582 | pointer to where the full notice is found. 583 | 584 | 585 | 586 | Copyright (C) 587 | 588 | This program is free software: you can redistribute it and/or modify it under 589 | the terms of the GNU General Public License as published by the Free Software 590 | Foundation, either version 3 of the License, or (at your option) any later 591 | version. 592 | 593 | This program is distributed in the hope that it will be useful, but WITHOUT 594 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 595 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 596 | 597 | You should have received a copy of the GNU General Public License along with 598 | this program. If not, see . 599 | 600 | Also add information on how to contact you by electronic and paper mail. 601 | 602 | If the program does terminal interaction, make it output a short notice like 603 | this when it starts in an interactive mode: 604 | 605 | Copyright (C) 606 | 607 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 608 | 609 | This is free software, and you are welcome to redistribute it under certain 610 | conditions; type `show c' for details. 611 | 612 | The hypothetical commands `show w' and `show c' should show the appropriate 613 | parts of the General Public License. Of course, your program's commands might 614 | be different; for a GUI interface, you would use an "about box". 615 | 616 | You should also get your employer (if you work as a programmer) or school, 617 | if any, to sign a "copyright disclaimer" for the program, if necessary. For 618 | more information on this, and how to apply and follow the GNU GPL, see . 619 | 620 | The GNU General Public License does not permit incorporating your program 621 | into proprietary programs. If your program is a subroutine library, you may 622 | consider it more useful to permit linking proprietary applications with the 623 | library. If this is what you want to do, use the GNU Lesser General Public 624 | License instead of this License. But first, please read . 626 | --------------------------------------------------------------------------------