;;; yaml-ts-mode.el --- tree-sitter support for YAML -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2025 Free Software Foundation, Inc. ;; Author : Randy Taylor ;; Maintainer : Randy Taylor ;; Created : December 2022 ;; Keywords : yaml languages tree-sitter ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; ;;; Code: (require 'treesit) (declare-function treesit-parser-create "treesit.c") (declare-function treesit-node-start "treesit.c") (declare-function treesit-node-end "treesit.c") (declare-function treesit-node-type "treesit.c") (declare-function treesit-node-child-by-field-name "treesit.c") (defvar yaml-ts-mode--syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?# "<" table) (modify-syntax-entry ?\n ">" table) (modify-syntax-entry ?& "." table) (modify-syntax-entry ?* "." table) (modify-syntax-entry ?\( "." table) (modify-syntax-entry ?\) "." table) (modify-syntax-entry ?\' "\"" table) table) "Syntax table for `yaml-ts-mode'.") (defvar yaml-ts-mode--font-lock-settings (treesit-font-lock-rules :language 'yaml :feature 'bracket '((["[" "]" "{" "}"]) @font-lock-bracket-face) :language 'yaml :feature 'comment '((comment) @font-lock-comment-face) :language 'yaml :feature 'constant '([(boolean_scalar) (null_scalar) (reserved_directive) (tag_directive) (yaml_directive)] @font-lock-constant-face) :language 'yaml :feature 'delimiter '((["," ":" "-" ">" "?" "|"]) @font-lock-delimiter-face) :language 'yaml :feature 'misc-punctuation '((["---" "..." "&" "*"]) @font-lock-misc-punctuation-face) :language 'yaml :feature 'number '([(float_scalar) (integer_scalar)] @font-lock-number-face) :language 'yaml :feature 'type '([(alias_name) (anchor_name) (tag)] @font-lock-type-face) :language 'yaml :feature 'string :override t '([(block_scalar) (double_quote_scalar) (single_quote_scalar) (string_scalar)] @font-lock-string-face) :language 'yaml :feature 'escape-sequence :override t '((escape_sequence) @font-lock-escape-face) :language 'yaml :feature 'property :override t '((block_mapping_pair key: (flow_node (plain_scalar (string_scalar) @font-lock-property-use-face))) (block_mapping_pair key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @font-lock-property-use-face)) (flow_mapping (_ key: (flow_node (plain_scalar (string_scalar) @font-lock-property-use-face)))) (flow_mapping (_ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @font-lock-property-use-face))) (flow_sequence (_ key: (flow_node (plain_scalar (string_scalar) @font-lock-property-use-face)))) (flow_sequence (_ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @font-lock-property-use-face)))) :language 'yaml :feature 'error :override t '((ERROR) @font-lock-warning-face)) "Tree-sitter font-lock settings for `yaml-ts-mode'.") (defun yaml-ts-mode--fill-paragraph (&optional justify) "Fill paragraph. Behaves like `fill-paragraph', but respects block node boundaries. JUSTIFY is passed to `fill-paragraph'." (interactive "*P") (save-restriction (widen) (let ((node (treesit-node-at (point)))) (if (member (treesit-node-type node) '("block_scalar" "comment")) (let* ((start (treesit-node-start node)) (end (treesit-node-end node)) (start-marker (point-marker)) (fill-paragraph-function nil)) (save-excursion (goto-char start) (forward-line) (move-marker start-marker (point)) (narrow-to-region (point) end)) (fill-region start-marker end justify)) t)))) (defun yaml-ts-mode--defun-name (node) "Return the defun name of NODE. Return nil if there is no name or if NODE is not a defun node." (when (equal (treesit-node-type node) "block_mapping_pair") (treesit-node-text (treesit-node-child-by-field-name node "key") t))) (defun yaml-ts-mode--outline-predicate (node) "Limit outlines to top-level mappings." (let ((regexp (rx (or "block_mapping_pair" "block_sequence_item")))) (when (string-match-p regexp (treesit-node-type node)) (not (treesit-node-top-level node regexp))))) ;;;###autoload (define-derived-mode yaml-ts-mode text-mode "YAML" "Major mode for editing YAML, powered by tree-sitter." :group 'yaml :syntax-table yaml-ts-mode--syntax-table (when (treesit-ready-p 'yaml) (setq treesit-primary-parser (treesit-parser-create 'yaml)) ;; Comments. (setq-local comment-start "# ") (setq-local comment-end "") ;; Indentation. (setq-local indent-tabs-mode nil) ;; Font-lock. (setq-local treesit-font-lock-settings yaml-ts-mode--font-lock-settings) (setq-local treesit-font-lock-feature-list '((comment) (string type) (constant escape-sequence number property) (bracket delimiter error misc-punctuation))) (setq-local fill-paragraph-function #'yaml-ts-mode--fill-paragraph) ;; Navigation. (setq-local treesit-defun-type-regexp "block_mapping_pair") (setq-local treesit-defun-name-function #'yaml-ts-mode--defun-name) (setq-local treesit-defun-tactic 'top-level) (setq-local treesit-thing-settings `((yaml (list ,(rx (or "block_mapping_pair" "flow_sequence"))) (sentence ,"block_mapping_pair")))) ;; Imenu. (setq-local treesit-simple-imenu-settings '((nil "\\`block_mapping_pair\\'" nil nil))) ;; Outline minor mode. (setq-local treesit-outline-predicate #'yaml-ts-mode--outline-predicate) (treesit-major-mode-setup) ;; Use the `list' thing defined above to navigate only lists ;; with `C-M-n', `C-M-p', `C-M-u', `C-M-d', but not sexps ;; with `C-M-f', `C-M-b' neither adapt to 'show-paren-mode' ;; that is problematic in languages without explicit ;; opening/closing nodes. (kill-local-variable 'forward-sexp-function) (kill-local-variable 'show-paren-data-function))) (derived-mode-add-parents 'yaml-ts-mode '(yaml-mode)) (if (treesit-ready-p 'yaml) (add-to-list 'auto-mode-alist '("\\.ya?ml\\'" . yaml-ts-mode))) (provide 'yaml-ts-mode) ;;; yaml-ts-mode.el ends here