aboutsummaryrefslogtreecommitdiff
path: root/.config/emacs/lisp/dumb-jump.el
diff options
context:
space:
mode:
Diffstat (limited to '.config/emacs/lisp/dumb-jump.el')
-rw-r--r--.config/emacs/lisp/dumb-jump.el5706
1 files changed, 5706 insertions, 0 deletions
diff --git a/.config/emacs/lisp/dumb-jump.el b/.config/emacs/lisp/dumb-jump.el
new file mode 100644
index 0000000..39e1f63
--- /dev/null
+++ b/.config/emacs/lisp/dumb-jump.el
@@ -0,0 +1,5706 @@
+;;; dumb-jump.el --- Jump to definition for 60+ languages without configuration -*- lexical-binding: t; -*-
+;; Copyright (C) 2015-2026 jack angers
+;; Author: jack angers and contributors
+;; Url: https://github.com/jacktasia/dumb-jump
+;; Version: 0.5.5
+;; Package-Requires: ((emacs "26.1"))
+;; Keywords: programming
+
+;; Dumb Jump 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, or (at your option)
+;; any later version.
+;;
+;; Dumb Jump 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 Dumb Jump. If not, see http://www.gnu.org/licenses.
+
+;;; Commentary:
+
+;; Dumb Jump is an Emacs "jump to definition" package with support for 60+ programming languages that favors
+;; "just working" over speed or accuracy. This means minimal -- and ideally zero -- configuration with absolutely
+;; no stored indexes (TAGS) or persistent background processes.
+;;
+;; Dumb Jump provides a xref-based interface for jumping to
+;; definitions. It is based on tools such as grep, the silver searcher
+;; (https://geoff.greer.fm/ag/), ripgrep
+;; (https://github.com/BurntSushi/ripgrep) or git-grep
+;; (https://git-scm.com/docs/git-grep).
+;;
+;; To enable Dumb Jump, add the following to your initialisation file:
+;;
+;; (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
+;;
+;; Now pressing M-. on an identifier should open a buffer at the place
+;; where it is defined, or a list of candidates if uncertain. This
+;; list can be navigated using M-g M-n (next-error) and M-g M-p
+;; (previous-error).
+;;; Code:
+(require 'xref)
+(require 'cl-generic)
+(require 'cl-lib)
+(require 'seq)
+(require 'subr-x)
+(require 'json)
+(require 'project nil 'noerror)
+
+(defun dumb-jump--chop-prefix (prefix text)
+ "Remove PREFIX from TEXT when present."
+ (if (and (stringp prefix)
+ (stringp text)
+ (string-prefix-p prefix text))
+ (substring text (length prefix))
+ text))
+
+(defun dumb-jump--contains-p (needle text &optional ignore-case)
+ "Return non-nil when TEXT contains NEEDLE as a literal substring.
+When IGNORE-CASE is non-nil, match without case sensitivity."
+ (and (stringp needle)
+ (stringp text)
+ (save-match-data
+ (let ((case-fold-search ignore-case))
+ (if (string-match-p (regexp-quote needle) text) t nil)))))
+
+(defun dumb-jump--index-of (needle text)
+ "Return the index of NEEDLE in TEXT, or nil when not found."
+ (when (and (stringp needle) (stringp text))
+ (save-match-data
+ (string-match-p (regexp-quote needle) text))))
+
+(defun dumb-jump--join (separator strings)
+ "Join STRINGS using SEPARATOR."
+ (mapconcat #'identity strings separator))
+
+(defun dumb-jump--match (regexp text &optional start)
+ "Return list of REGEXP matches in TEXT from START, or nil."
+ (when (and (stringp regexp)
+ (stringp text))
+ (save-match-data
+ (when (string-match regexp text start)
+ (let* ((group-count (/ (length (match-data t)) 2))
+ (i 0)
+ matches)
+ (while (< i group-count)
+ (push (match-string i text) matches)
+ (setq i (1+ i)))
+ (nreverse matches))))))
+
+(defun dumb-jump--matched-positions-all (regexp text &optional start)
+ "Return a list of match ranges for REGEXP in TEXT from START.
+Each element is a cons cell of the form (START . END)."
+ (when (and (stringp regexp) (stringp text))
+ (save-match-data
+ (let ((pos (or start 0))
+ matches)
+ (while (string-match regexp text pos)
+ (let ((beg (match-beginning 0))
+ (end (match-end 0)))
+ (push (cons beg end) matches)
+ ;; Avoid infinite loops if REGEXP can match empty strings.
+ (setq pos (if (= beg end) (1+ end) end))))
+ (nreverse matches)))))
+
+(defun dumb-jump--replace (old new text)
+ "Replace all OLD literal matches in TEXT with NEW."
+ (replace-regexp-in-string (regexp-quote old) (or new "") (or text "") t t))
+
+(defun dumb-jump--split (regexp text &optional omit-nulls)
+ "Split TEXT on REGEXP and optionally OMIT-NULLS."
+ (split-string (or text "") regexp omit-nulls))
+
+(defun dumb-jump--trim (text)
+ "Trim leading and trailing whitespace from TEXT."
+ (string-trim (or text "")))
+
+(defun dumb-jump--select-choice (choices &optional prompt)
+ "Choose one of CHOICES with `completing-read'.
+PROMPT defaults to \"Jump to: \"."
+ (completing-read (or prompt "Jump to: ") choices nil t))
+
+(defun dumb-jump--show-preview (text)
+ "Display TEXT preview to the user."
+ (message "%s" text))
+
+(defun dumb-jump--find-index (pred list)
+ "Return index of first element in LIST satisfying PRED, or nil."
+ (let ((i 0))
+ (catch 'found
+ (dolist (elem list)
+ (when (funcall pred elem)
+ (throw 'found i))
+ (setq i (1+ i)))
+ nil)))
+
+(defun dumb-jump--zip-pair (list1 list2)
+ "Return list of cons pairs from LIST1 and LIST2."
+ (cl-mapcar #'cons list1 list2))
+
+(defun dumb-jump--splice (pred rep list)
+ "Replace elements in LIST matching PRED with result of calling REP on them.
+REP can return a list to splice in multiple elements."
+ (apply #'append
+ (mapcar (lambda (it)
+ (if (funcall pred it)
+ (funcall rep it)
+ (list it)))
+ list)))
+
+(defgroup dumb-jump nil
+ "Easily jump to project function and variable definitions."
+ :group 'tools
+ :group 'convenience)
+
+;;;###autoload
+(defvar dumb-jump-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "C-M-g") 'dumb-jump-go)
+ (define-key map (kbd "C-M-p") 'dumb-jump-back)
+ (define-key map (kbd "C-M-q") 'dumb-jump-quick-look)
+ map))
+
+(defvar dumb-jump--search-mode 'definitions
+ "Current search mode: `definitions' (default) or `references'.")
+
+(defcustom dumb-jump-window
+ 'current
+ "Which window to use when jumping.
+Valid options are \\='current (default) or \\='other."
+ :group 'dumb-jump
+ :type '(choice (const :tag "Current window" current)
+ (const :tag "Other window" other)))
+
+(defcustom dumb-jump-use-visible-window
+ t
+ "If true will jump in a visible window that shows the opened file."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-selector
+ 'completing-read
+ "Which selector to use when there is multiple choices.
+The available selectors are:
+- helm : https://emacs-helm.github.io/helm/
+- ivy : https://github.com/abo-abo/swiper
+- Completing Read: Emacs default `completing-read'.
+
+When selecting helm or ivy, if the corresponding package is not installed,
+the selector falls back to `completing-read'."
+ :group 'dumb-jump
+ :type '(choice (const :tag "Helm" helm)
+ (const :tag "Ivy" ivy)
+ (const :tag "Completing Read" completing-read)))
+
+(defcustom dumb-jump-ivy-jump-to-selected-function
+ #'dumb-jump-ivy-jump-to-selected
+ "Prompts user for a choice using ivy then dumb-jump to that choice."
+ :group 'dumb-jump
+ :type 'function)
+
+(defcustom dumb-jump-prefer-searcher nil
+ "The preferred searcher command line tool to use.
+The available choices are:
+- \\='ag : https://github.com/ggreer/the_silver_searcher
+- \\='rg : https://github.com/BurntSushi/ripgrep
+- \\='grep : https://en.wikipedia.org/wiki/Grep
+- \\='gnu-grep : https://www.gnu.org/software/grep/manual/grep.html
+- \\='git-grep : https://git-scm.com/docs/git-grep
+- \\='git-grep-plus-ag
+
+If nil, select the most optimal searcher at runtime, looking for all
+tools listed above except for git-grep in the order of that list.
+However, the choice is overridden by `dumb-jump-force-searcher' selection
+unless that is nil."
+ :group 'dumb-jump
+ :type '(choice (const :tag "Best Available" nil)
+ (const :tag "ag" ag)
+ (const :tag "rg" rg)
+ (const :tag "grep" grep)
+ (const :tag "gnu-grep" gnu-grep)
+ (const :tag "git grep" git-grep)
+ (const :tag "git grep + ag" git-grep-plus-ag)))
+
+(defcustom dumb-jump-force-searcher nil
+ "Search tool override of `dumb-jump-prefer-searcher' selection.
+
+By default this is nil to honour the choice made by `dumb-jump-prefer-searcher'.
+
+However, you may want to override that choice for some projects by identifying
+one of two preferred choice overriding methods:
+- Override preferred via user-specified function:
+ Enter the name of a function that takes the current directory and return the
+ search tool symbol to use for this project or nil if the choice made in
+ `dumb-jump-prefer-searcher' must be honoured.
+- Override preferred for directories needing git-grep:
+ Specify one or more project directories where the git-grep search tool must
+ be used.
+- Override to a specific tool. That is mostly useful to set the overriding
+ inside the .dir-locals.el of the directory.
+ The choices are: \\='ag, \\='rg, \\='grep, \\='gnu-grep, \\='git-grep, or
+ \\='git-grep-plus-ag."
+ :group 'dumb-jump
+ :type '(choice (const
+ :tag "nil: honour `dumb-jump-prefer-searcher' choice." nil)
+ (function
+ :tag "Override preferred via user-specified function")
+ (repeat
+ :tag "Override preferred for directories needing git-grep"
+ (directory :tag "Repo root"))
+ (const :tag "ag" ag)
+ (const :tag "rg" rg)
+ (const :tag "grep" grep)
+ (const :tag "gnu grep" gnu-grep)
+ (const :tag "git grep" git-grep)
+ (const :tag "git grep + ag" git-grep-plus-ag)))
+
+(defcustom dumb-jump-grep-prefix
+ "LANG=C"
+ "Prefix to grep command. Seemingly makes it faster for pure text."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-grep-cmd
+ "grep"
+ "The path to grep. By default assumes it is in path."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-ag-cmd
+ "ag"
+ "The path to the silver searcher. By default assumes it is in path."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-rg-cmd
+ "rg"
+ "The path to ripgrep. By default assumes it is in path."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-git-grep-cmd
+ "git grep"
+ "The path to git grep. By default assumes it is in path."
+ :group 'dumb-jump
+ :type 'string)
+
+;; ---------------------------------------------------------------------------
+;; Dumb Jump Regular Expressions (see `dumb-jump-find-rules' docstring)
+
+(defcustom dumb-jump-force-using-space-bracket-exp
+ (eq system-type 'windows-nt)
+ "When t the `\\\\s` in regexp code strings is replaced by `[[:space:]]`.
+Otherwise it is left in the regular expression.
+Defaults to t under Windows because older gnu grep versions have problem
+dealing with `\\\\s` to match end of words due to the 2 bytes used to
+identify the end of line."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defun dumb-jump-use-space-bracket-exp-for (variant)
+ "Return t when `\\\\s` must be replaced by `[[:space]]` for VARIANT.
+VARIANT must be a one of the following symbols: ag, rg, grep, gnu-grep,
+git-grep or git-grep-plus-ag."
+ (and dumb-jump-force-using-space-bracket-exp
+ (memq variant '(grep gnu-grep git-grep git-grep-plus-ag))))
+
+;; word-boundary regexps: they replace the "\\j" in dumb-jump regexes.
+;; [:todo 2026-01-29, by Pierre Rouleau: Should we not exclude '_' from word boundary?]
+(defcustom dumb-jump-ag-word-boundary
+ "(?![a-zA-Z0-9\\?\\*-])"
+ "Regexp that replaces `\\j` in dumb-jump regexes for ag search.
+`\\b` thinks `-` is a word boundary.
+When this matters use `\\j` instead and ag will use this value."
+ :group 'dumb-jump
+ :type 'string)
+
+;; [:todo 2026-01-29, by Pierre Rouleau: Should we not exclude '_' from word boundary?]
+(defcustom dumb-jump-rg-word-boundary
+ "($|[^a-zA-Z0-9\\?\\*-])"
+ "Regexp that replaces `\\j` in dumb-jump regexes for rg search.
+`\\b` thinks `-` is a word boundary.
+When this matters use `\\j` instead and rg will use this value."
+ :group 'dumb-jump
+ :type 'string)
+
+;; [:todo 2026-01-29, by Pierre Rouleau: Should we not exclude '_' from word boundary?]
+(defcustom dumb-jump-git-grep-word-boundary
+ "($|[^a-zA-Z0-9\\?\\*-])"
+ "Regexp that replaces `\\j` in dumb-jump regexes for git-grep search.
+`\\b` thinks `-` is a word boundary.
+When this matters use `\\j` instead and git grep will use this value."
+ :group 'dumb-jump
+ :type 'string)
+
+;; [:todo 2026-01-29, by Pierre Rouleau: Should we not exclude '_' from word boundary?]
+(defcustom dumb-jump-grep-word-boundary
+ "($|[^a-zA-Z0-9\\?\\*-])"
+ "Regexp that replaces `\\j` in dumb-jump regexes for grep search.
+`\\b` thinks `-` is a word boundary.
+When this matters use `\\j` instead and grep will use this value."
+ :group 'dumb-jump
+ :type 'string)
+
+;; [:todo 2026-01-29, by Pierre Rouleau: Should we not exclude '_' from word boundary?]
+(defcustom dumb-jump-gnu-grep-word-boundary
+ "($|[^a-zA-Z0-9\\?\\*-])"
+ "Regexp that replaces `\\j` in dumb-jump regexes for gnu grep search.
+`\\b` thinks `-` is a word boundary.
+When this matters use `\\j` instead and grep will use this value."
+ :group 'dumb-jump
+ :type 'string)
+
+;; --
+
+(defcustom dumb-jump-fallback-regex
+ "\\bJJJ\\j"
+ "When `dumb-jump-fallback-search' is t use this regex.
+Defaults to boundary search of symbol under point."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-fallback-search
+ t
+ "If nothing is found with normal search fallback searching fallback regex."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-zgrep-cmd
+ "zgrep"
+ "The path to grep to use for gzipped files.
+By default assumes it is in path."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-grep-args "-REn"
+ "Grep command args [R]ecursive, [E]xtended regexes, and show line [n]umbers."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-gnu-grep-args "-rEn"
+ "Grep command args [r]ecursive and [E]xtended regexes, and show line [n]umbers."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-max-find-time
+ 2
+ "Number of seconds a grep/find command can take before issuing a warning.
+The warning recommend using a faster search tool and config."
+ :group 'dumb-jump
+ :type 'integer)
+
+(defcustom dumb-jump-functions-only
+ nil
+ "Should we only jump to functions?"
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-quiet
+ nil
+ "If non-nil Dumb Jump will not log anything to *Messages*.
+Note that it prevents printing when `dumb-jump-debug' is non-nil."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-ignore-context
+ nil
+ "If non-nil Dumb Jump will ignore the context of point when jumping."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-git-grep-search-untracked
+ t
+ "If non-nil Dumb Jump will also search untracked files when using git-grep."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-git-grep-search-args
+ ""
+ "Appends the passed arguments to the git-grep search function.
+Default: \"\"."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-ag-search-args
+ ""
+ "Appends the passed arguments to the ag search function.
+Default: \"\"."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-rg-search-args
+ "--pcre2"
+ "Appends the passed arguments to the rg search function.
+Default: \"--pcre2\"."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-search-type-org-only-org
+ t
+ "If non nil restrict type ag/rg to org file.
+If nil add also the language type of current src block."
+ :group 'dumb-jump
+ :type 'boolean)
+
+;; [:todo 2026-01-29, by Pierre Rouleau: Why 3 backslashes in front of '('? ]
+(defcustom dumb-jump-find-rules
+ ;;-- elisp
+ '((:language "elisp" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\((defun|cl-defun|cl-defgeneric|cl-defmethod|cl-defsubst)\\s+JJJ\\j"
+ :tests ("(defun test (blah)"
+ "(defun test\n"
+ "(cl-defun test (blah)"
+ "(cl-defun test\n")
+ :not ("(defun test-asdf (blah)"
+ "(defun test-blah\n"
+ "(cl-defun test-asdf (blah)"
+ "(cl-defun test-blah\n"
+ "(defun tester (blah)"
+ "(defun test? (blah)"
+ "(defun test- (blah)"))
+
+ (:language "elisp" :type "function" ; macros
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\((defmacro|cl-defmacro|cl-define-compiler-macro)\\s+JJJ\\j"
+ :tests ("(defmacro test (blah)"
+ "(defmacro test\n")
+ :not ("(defmacro test-asdf (blah)"
+ "(defmacro test-blah\n"
+ "(defmacro tester (blah)"
+ "(defmacro test? (blah)"
+ "(defmacro test- (blah)"))
+
+ (:language "elisp" :type "function" ; hydras
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(defhydra\\b\\s*JJJ\\j"
+ :tests ("(defhydra test "
+ "(defhydra test\n"
+ "(defhydra test (blah\n"
+ "(defhydra test (blah)")
+ :not ("(defhydra tester"
+ "(defhydra test?"
+ "(defhydra test-"
+ "(defhydra test? (blah\n"
+ "(defhydra test? (blah)"))
+
+ (:language "elisp" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(defvar(-local)?\\b\\s*JJJ\\j"
+ :tests ("(defvar test "
+ "(defvar test\n"
+ "(defvar-local test"
+ "(defvar-local test\n")
+ :not ("(defvar tester"
+ "(defvar test?"
+ "(defvar test-"
+ "(defvar-local tester"
+ "(defvar-local test?"
+ "(defvar-local test-"))
+
+ (:language "elisp" :type "variable"
+ :supports ("ag" "grep" "rg" "ugrep" "git-grep")
+ :regex "\\(defconst\\b\\s*JJJ\\j"
+ :tests ("(defconst test "
+ "(defconst test\n")
+ :not ("(defconst tester"
+ "(defconst test?"
+ "(defconst test-"))
+
+ (:language "elisp" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(defcustom\\b\\s*JJJ\\j"
+ :tests ("(defcustom test "
+ "(defcustom test\n")
+ :not ("(defcustom tester"
+ "(defcustom test?"
+ "(defcustom test-"))
+
+ (:language "elisp" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(setq\\b\\s*JJJ\\j"
+ :tests ("(setq test 123)")
+ :not ("setq test-blah 123)"
+ "(setq tester"
+ "(setq test?" "(setq test-"))
+
+ (:language "elisp" :type "variable" ; let bound variables
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(JJJ\\s+"
+ :skip-ref-filter t
+ :tests ("(let ((test 123)))"
+ "(let* ((test 123)))")
+ :not ("(let ((test-2 123)))"
+ "(let* ((test-2 123)))"))
+
+ (:language "elisp" :type "type" ; cl-lib structured type definitions
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\((cl-defstruct|cl-deftype)\\s+JJJ\\j"
+ :tests ("(cl-defstruct test "
+ "(cl-defstruct test\n"
+ "(cl-deftype test "
+ "(cl-deftype test\n")
+ :not ("(cl-defstruct test-asdf (blah)"
+ "(cl-defstruct test-blah\n"
+ "(cl-deftype test-asdf (blah)"
+ "(cl-deftype test-blah\n"))
+
+ (:language "elisp" :type "variable" ;; variable in method signature
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\((defun|cl-defun|cl-defgeneric|cl-defmethod)\\s*.+\\(?\\s*JJJ\\j\\s*\\)?"
+ :tests ("(defun blah (test)"
+ "(defun blah (test blah)"
+ "(defun (blah test)")
+ :not ("(defun blah (test-1)"
+ "(defun blah (test-2 blah)"
+ "(defun (blah test-3)"))
+
+ ;;-- common lisp
+ (:language "commonlisp" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(def(un|macro|generic|method|setf)\\s+JJJ\\j"
+ ;; \\j usage see `dumb-jump-ag-word-boundary`
+ :tests ("(defun test (blah)"
+ "(defun test\n"
+ "(defmacro test (blah)"
+ "(defmacro test\n"
+ "(defgeneric test (blah)"
+ "(defgeneric test\n"
+ "(defmethod test (blah)"
+ "(defmethod test\n"
+ "(defsetf test (blah)"
+ "(defsetf test\n")
+ :not ("(defun test-asdf (blah)"
+ "(defun test-blah\n"
+ "(defun tester (blah)"
+ "(defun test? (blah)"
+ "(defun test- (blah)"
+ "(defmacro test-asdf (blah)"
+ "(defmacro test-blah\n"
+ "(defmacro tester (blah)"
+ "(defmacro test? (blah)"
+ "(defmacro test- (blah)"
+ "(defgeneric test-asdf (blah)"
+ "(defgeneric test-blah\n"
+ "(defgeneric tester (blah)"
+ "(defgeneric test? (blah)"
+ "(defun test- (blah)"
+ "(defmethod test-asdf (blah)"
+ "(defmethod test-blah\n"
+ "(defmethod tester (blah)"
+ "(defmethod test? (blah)"
+ "(defun test- (blah)"
+ "(defsetf test-asdf (blah)"
+ "(defsetf test-blah\n"
+ "(defsetf tester (blah)"
+ "(defsetf test? (blah)"
+ "(defun test- (blah)"))
+
+ (:language "commonlisp" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define-(modify-macro|compiler-macro|setf-expander)\\s+JJJ\\j"
+ ;; \\j usage see `dumb-jump-ag-word-boundary`
+ :tests ("(define-modify-macro test (blah)"
+ "(define-modify-macro test\n"
+ "(define-compiler-macro test (blah)"
+ "(define-compiler-macro test\n")
+ :not ("(define-modify-macro test-asdf (blah)"
+ "(define-modify-macro test-blah\n"
+ "(define-modify-macro tester (blah)"
+ "(define-modify-macro test? (blah)"
+ "(define-modify-macro test- (blah)"
+ "(define-compiler-macro test-asdf (blah)"
+ "(define-compiler-macro test-blah\n"
+ "(define-compiler-macro tester (blah)"
+ "(define-compiler-macro test? (blah)"
+ "(define-compiler-macro test- (blah)"))
+
+ (:language "commonlisp" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(def(var|parameter|constant)\\b\\s*JJJ\\j"
+ :tests ("(defvar test "
+ "(defvar test\n"
+ "(defparameter test "
+ "(defparameter test\n"
+ "(defconstant test "
+ "(defconstant test\n")
+ :not ("(defvar tester"
+ "(defvar test?"
+ "(defvar test-"
+ "(defparameter tester"
+ "(defparameter test?"
+ "(defparameter test-"
+ "(defconstant tester"
+ "(defconstant test?"
+ "(defconstant test-"))
+
+ (:language "commonlisp" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define-symbol-macro\\b\\s*JJJ\\j"
+ :tests ("(define-symbol-macro test "
+ "(define-symbol-macro test\n")
+ :not ("(define-symbol-macro tester"
+ "(define-symbol-macro test?"
+ "(define-symbol-macro test-"))
+
+ (:language "commonlisp" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(def(class|struct|type)\\b\\s*JJJ\\j"
+ :tests ("(defclass test "
+ "(defclass test\n"
+ "(defstruct test "
+ "(defstruct test\n"
+ "(deftype test "
+ "(deftype test\n")
+ :not ("(defclass tester"
+ "(defclass test?"
+ "(defclass test-"
+ "(defstruct tester"
+ "(defstruct test?"
+ "(defstruct test-"
+ "(deftype tester"
+ "(deftype test?"
+ "(deftype test-"))
+
+ (:language "commonlisp" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define-condition\\b\\s*JJJ\\j"
+ :tests ("(define-condition test "
+ "(define-condition test\n")
+ :not ("(define-condition tester"
+ "(define-condition test?"
+ "(define-condition test-"))
+
+ ;;-- racket
+ (:language "racket" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+\\(\\s*JJJ\\j"
+ :tests ("(define (test blah)"
+ "(define (test\n")
+ :not ("(define test blah"
+ "(define (test-asdf blah)"
+ "(define test (lambda (blah"))
+
+ (:language "racket" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+JJJ\\s*\\\(\\s*lambda"
+ :tests ("(define test (lambda (blah"
+ "(define test (lambda\n")
+ :not ("(define test blah"
+ "(define test-asdf (lambda (blah)"
+ "(define (test)"
+ "(define (test blah) (lambda (foo"))
+
+ (:language "racket" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(let\\s+JJJ\\s*(\\\(|\\\[)*"
+ :tests ("(let test ((blah foo) (bar bas))"
+ "(let test\n"
+ "(let test [(foo")
+ :not ("(let ((test blah"))
+
+ (:language "racket" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+JJJ\\j"
+ :tests ("(define test " "(define test\n")
+ :not ("(define (test"))
+
+ (:language "racket" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(\\\(|\\\[)\\s*JJJ\\s+"
+ :tests ("(let ((test 'foo"
+ "(let [(test 'foo"
+ "(let [(test 'foo"
+ "(let [[test 'foo"
+ "(let ((blah 'foo) (test 'bar)")
+ :not ("{test foo"))
+
+ (:language "racket" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(lambda\\s+\\\(?[^\(\)]*\\s*JJJ\\j\\s*\\\)?"
+ :tests ("(lambda (test)"
+ "(lambda (foo test)"
+ "(lambda test (foo)")
+ :not ("(lambda () test"))
+
+ (:language "racket" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+\\\([^\(\)]+\\s*JJJ\\j\\s*\\\)?"
+ :tests ("(define (foo test)"
+ "(define (foo test bar)")
+ :not ("(define foo test"
+ "(define (test foo"
+ "(define (test)"))
+
+ (:language "racket" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(struct\\s+JJJ\\j"
+ :tests ("(struct test (a b)"))
+
+ ;;-- scheme
+ (:language "scheme" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+\\(\\s*JJJ\\j"
+ :tests ("(define (test blah)"
+ "(define (test\n")
+ :not ("(define test blah"
+ "(define (test-asdf blah)"
+ "(define test (lambda (blah"))
+
+ (:language "scheme" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+JJJ\\s*\\\(\\s*lambda"
+ :tests ("(define test (lambda (blah"
+ "(define test (lambda\n")
+ :not ("(define test blah"
+ "(define test-asdf (lambda (blah)"
+ "(define (test)"
+ "(define (test blah) (lambda (foo"))
+
+ (:language "scheme" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(let\\s+JJJ\\s*(\\\(|\\\[)*"
+ :tests ("(let test ((blah foo) (bar bas))"
+ "(let test\n"
+ "(let test [(foo")
+ :not ("(let ((test blah"))
+
+ (:language "scheme" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+JJJ\\j"
+ :tests ("(define test "
+ "(define test\n")
+ :not ("(define (test"))
+
+ (:language "scheme" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(\\\(|\\\[)\\s*JJJ\\s+"
+ :tests ("(let ((test 'foo"
+ "(let [(test 'foo"
+ "(let [(test 'foo"
+ "(let [[test 'foo"
+ "(let ((blah 'foo) (test 'bar)")
+ :not ("{test foo"))
+
+ (:language "scheme" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(lambda\\s+\\\(?[^\(\)]*\\s*JJJ\\j\\s*\\\)?"
+ :tests ("(lambda (test)"
+ "(lambda (foo test)"
+ "(lambda test (foo)")
+ :not ("(lambda () test"))
+
+ (:language "scheme" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\(define\\s+\\\([^\(\)]+\\s*JJJ\\j\\s*\\\)?"
+ :tests ("(define (foo test)"
+ "(define (foo test bar)")
+ :not ("(define foo test"
+ "(define (test foo"
+ "(define (test)"))
+
+ ;;-- janet
+ (:language "janet" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(\(de\)?f\\s+JJJ\\j"
+ :tests ("(def test (foo)"))
+
+ (:language "janet" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(var\\s+JJJ\\j"
+ :tests ("(var test (foo)"))
+
+ (:language "janet" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(\(de\)fn-?\\s+JJJ\\j"
+ :tests ("(defn test [foo]"
+ "(defn- test [foo]")
+ :not ("(defn test? [foo]"
+ "(defn- test? [foo]"))
+
+ (:language "janet" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(defmacro\\s+JJJ\\j"
+ :tests ("(defmacro test [foo]"))
+
+ ;;-- c++
+ (:language "c++" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "\\bJJJ(\\s|\\))*\\((\\w|[,&*.<>:]|\\s)*(\\))\\s*(const|->|\\{|$)|typedef\\s+(\\w|[(*]|\\s)+JJJ(\\)|\\s)*\\("
+ :tests ("int test(){"
+ "my_struct (*test)(int a, int b){"
+ "auto MyClass::test ( Builder::Builder& reference, ) -> decltype( builder.func() ) {"
+ "int test( int *random_argument) const {"
+ "test::test() {"
+ "typedef int (*test)(int);")
+ :not ("return test();)"
+ "int test(a, b);"
+ "if( test() ) {"
+ "else test();"))
+
+ ;; (:language "c++" :type "variable"
+ ;; :supports ("grep")
+ ;; :regex "(\\b\\w+|[,>])([*&]|\\s)+JJJ\\s*(\\[([0-9]|\\s)*\\])*\\s*([=,){;]|:\\s*[0-9])|#define\\s+JJJ\\b"
+ ;; :tests ("int test=2;" "char *test;" "int x = 1, test = 2" "int test[20];" "#define test" "unsigned int test:2;"))
+
+ (:language "c++" :type "variable"
+ :supports ("ag" "rg")
+ :regex "\\b(?!(class\\b|struct\\b|return\\b|else\\b|delete\\b))(\\w+|[,>])([*&]|\\s)+JJJ\\s*(\\[(\\d|\\s)*\\])*\\s*([=,(){;]|:\\s*\\d)|#define\\s+JJJ\\b"
+ :tests ("int test=2;"
+ "char *test;"
+ "int x = 1, test = 2"
+ "int test[20];"
+ "#define test"
+ "typedef int test;"
+ "unsigned int test:2")
+ :not ("return test;"
+ "#define NOT test"
+ "else test=2;"))
+
+ (:language "c++" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "\\b(class|struct|enum|union)\\b\\s*JJJ\\b\\s*(final\\s*)?(:((\\s*\\w+\\s*::)*\\s*\\w*\\s*<?(\\s*\\w+\\s*::)*\\w+>?\\s*,*)+)?((\\{|$))|}\\s*JJJ\\b\\s*;"
+ :tests ("typedef struct test {"
+ "enum test {"
+ "} test;"
+ "union test {"
+ "class test final: public Parent1, private Parent2{"
+ "class test : public std::vector<int> {")
+ :not("union test var;"
+ "struct test function() {"))
+
+ ;;-- clojure
+ (:language "clojure" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(def.*\ JJJ\\j"
+ :tests ("(def test (foo)"
+ "(defn test [foo]"
+ "(defn ^:some-data test [foo]"
+ "(defn- test [foo]"
+ "(defmacro test [foo]"
+ "(deftask test [foo]"
+ "(deftype test [foo]"
+ "(defmulti test fn"
+ "(defmethod test type"
+ "(definterface test (foo)"
+ "(defprotocol test (foo)"
+ "(defrecord test [foo]"
+ "(deftest test"))
+
+ ;;-- coffeescript
+ (:language "coffeescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*JJJ\\s*[=:].*[-=]>"
+ :tests ("test = () =>"
+ "test= =>"
+ "test = ->"
+ "test=()->"
+ "test : () =>"
+ "test: =>"
+ "test : ->"
+ "test:()->")
+ :not ("# test = =>"
+ "test = 1"))
+
+ (:language "coffeescript" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*JJJ\\s*[:=][^:=-][^>]+$"
+ :tests ("test = $"
+ "test : ["
+ "test = {"
+ "test = a")
+ :not ("test::a"
+ "test: =>"
+ "test == 1"
+ "# test = 1"))
+
+ (:language "coffeescript" :type "class"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*\\bclass\\s+JJJ"
+ :tests ("class test"
+ "class test extends")
+ :not ("# class"))
+
+ ;;-- objc : Objective-C
+ (:language "objc" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\)\\s*JJJ(:|\\b|\\s)"
+ :tests ("- (void)test"
+ "- (void)test:(UIAlertView *)alertView")
+ :not ("- (void)testnot"
+ "- (void)testnot:(UIAlertView *)alertView"))
+
+ (:language "objc" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\b\\*?JJJ\\s*=[^=\\n]+"
+ :tests ("NSString *test = @\"asdf\"")
+ :not ("NSString *testnot = @\"asdf\""
+ "NSString *nottest = @\"asdf\""))
+
+ (:language "objc" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(@interface|@protocol|@implementation)\\b\\s*JJJ\\b\\s*"
+ :tests ("@interface test: UIWindow")
+ :not ("@interface testnon: UIWindow"))
+
+
+ (:language "objc" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "typedef\\b\\s+(NS_OPTIONS|NS_ENUM)\\b\\([^,]+?,\\s*JJJ\\b\\s*"
+ :tests ("typedef NS_ENUM(NSUInteger, test)")
+ :not ("typedef NS_ENUMD(NSUInteger, test)"))
+
+ ;;-- swift
+ (:language "swift" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(let|var)\\s*JJJ\\s*(=|:)[^=:\\n]+"
+ :tests ("let test = 1234"
+ "var test = 1234"
+ "private lazy var test: UITapGestureRecognizer")
+ :not ("if test == 1234:"))
+
+ (:language "swift" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "func\\s+JJJ\\b\\s*(<[^>]*>)?\\s*\\("
+ :tests ("func test(asdf)"
+ "func test()"
+ "func test<Value: Protocol>()")
+ :not ("func testnot(asdf)"
+ "func testnot()"))
+
+ (:language "swift" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(class|struct|protocol|enum)\\s+JJJ\\b\\s*?"
+ :tests ("struct test"
+ "struct test: Codable"
+ "struct test<Value: Codable>"
+ "class test:"
+ "class test: UIWindow"
+ "class test<Value: Codable>")
+ :not ("class testnot:"
+ "class testnot(object):"
+ "struct testnot(object)"))
+
+ (:language "swift" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(typealias)\\s+JJJ\\b\\s*?="
+ :tests ("typealias test =")
+ :not ("typealias testnot"))
+
+ ;;-- csharp : C#
+ (:language "csharp" :type "function"
+ :supports ("ag" "rg")
+ :regex "^\\s*(?:[\\w\\[\\]]+\\s+){1,3}JJJ\\s*\\\("
+ :tests ("int test()"
+ "int test(param)"
+ "static int test()"
+ "static int test(param)"
+ "public static MyType test()"
+ "private virtual SomeType test(param)"
+ "static int test()")
+ :not ("test()"
+ "testnot()"
+ "blah = new test()"))
+
+ (:language "csharp" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n)]+"
+ :tests ("int test = 1234")
+ :not ("if test == 1234:"
+ "int nottest = 44"))
+
+ (:language "csharp" :type "variable"
+ :supports ("ag" "rg")
+ :regex "\\bforeach\\s*\\(\\s*[\\w\\[\\]<>,\\.?]+\\s+JJJ\\s+in\\b"
+ :tests ("foreach (int test in items)"
+ "foreach (var test in items)"
+ "foreach (string test in items)"
+ "foreach (List<int> test in items)")
+ :not ("foreach (int nottest in items)"
+ "test in items"))
+
+ (:type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :language "csharp"
+ :regex "(class|interface)\\s*JJJ\\b"
+ :tests ("class test:"
+ "public class test : IReadableChannel, I")
+ :not ("class testnot:"
+ "public class testnot : IReadableChannel, I"))
+
+ ;;-- java (literally the same regexes as c#, but different tests)
+ (:language "java" :type "function"
+ :supports ("ag" "rg")
+ :regex "^\\s*(?:[\\w\\[\\]]+\\s+){1,3}JJJ\\s*\\\("
+ :tests ("int test()"
+ "int test(param)"
+ "static int test()"
+ "static int test(param)"
+ "public static MyType test()"
+ "private virtual SomeType test(param)"
+ "static int test()"
+ "private foo[] test()")
+ :not ("test()"
+ "testnot()"
+ "blah = new test()"
+ "foo bar = test()"))
+
+ (:language "java" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n)]+"
+ :tests ("int test = 1234")
+ :not ("if test == 1234:"
+ "int nottest = 44"))
+
+ (:language "java" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(class|interface)\\s*JJJ\\b"
+ :tests ("class test:"
+ "public class test implements Something")
+ :not ("class testnot:"
+ "public class testnot implements Something"))
+
+ (:language "java" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\benum\\s+JJJ\\b"
+ :tests ("enum test {"
+ "public enum test {")
+ :not ("enum testnot {"))
+
+ (:language "java" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "@interface\\s+JJJ\\b"
+ :tests ("@interface test {"
+ "public @interface test {")
+ :not ("@interface testnot {"))
+
+ ;; grep/git-grep fallback: requires access modifier because grep lacks
+ ;; PCRE features (?:...) and {n,m} used by the ag/rg rule above.
+ (:language "java" :type "function"
+ :supports ("grep" "git-grep")
+ :regex "(public|protected|private|static)[^(]+JJJ\\s*\\\("
+ :tests ("public int test()"
+ "private static String test(param)"
+ "protected List<String> test()")
+ :not ("test()"
+ "new test()"))
+
+ ;;-- vala (again just like c#, exactly the same..)
+ (:language "vala" :type "function"
+ :supports ("ag" "rg")
+ :regex "^\\s*(?:[\\w\\[\\]]+\\s+){1,3}JJJ\\s*\\\("
+ :tests ("int test()"
+ "int test(param)"
+ "static int test()"
+ "static int test(param)"
+ "public static MyType test()"
+ "private virtual SomeType test(param)"
+ "static int test()")
+ :not ("test()"
+ "testnot()"
+ "blah = new test()"))
+
+ (:language "vala" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n)]+"
+ :tests ("int test = 1234")
+ :not ("if test == 1234:"
+ "int nottest = 44"))
+
+ (:language "vala" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(class|interface)\\s*JJJ\\b"
+ :tests ("class test:"
+ "public class test : IReadableChannel, I")
+ :not ("class testnot:"
+ "public class testnot : IReadableChannel, I"))
+
+ ;;-- coq (renamed Rocq in March 2025)
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Variable\\s+JJJ\\b"
+ :tests ("Variable test")
+ :not ("Variable testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Inductive\\s+JJJ\\b"
+ :tests ("Inductive test")
+ :not ("Inductive testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Lemma\\s+JJJ\\b"
+ :tests ("Lemma test")
+ :not ("Lemma testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Definition\\s+JJJ\\b"
+ :tests ("Definition test")
+ :not ("Definition testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Hypothesis\\s+JJJ\\b"
+ :tests ("Hypothesis test")
+ :not ("Hypothesis testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Theorem\\s+JJJ\\b"
+ :tests ("Theorem test")
+ :not ("Theorem testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Fixpoint\\s+JJJ\\b"
+ :tests ("Fixpoint test")
+ :not ("Fixpoint testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*Module\\s+JJJ\\b"
+ :tests ("Module test")
+ :not ("Module testx"))
+
+ (:language "coq" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "\\s*CoInductive\\s+JJJ\\b"
+ :tests ("CoInductive test")
+ :not ("CoInductive testx"))
+
+ ;;-- python
+ (:language "python" :type "variable"
+ :supports ("ag" "rg")
+ :regex "\\s*\\bJJJ\\b(\\s*:[^=\\n]+)?\\s*=[^=\\n]+"
+ :tests ("test = 1234"
+ "test: int = 1234"
+ "test: int | None = 1234"
+ "test: List[Optional[int]] = [1234, None]")
+ :not ("if test == 1234:"
+ "_test = 1234"
+ "if (test := 3):"
+ "while test:"
+ "test['key'] = 1234"))
+
+ (:language "python" :type "variable"
+ :supports ("grep" "git-grep")
+ :regex "\\s*\\bJJJ\\b(\\s*:[^=]+)?\\s*=[^=]+"
+ :tests ("test = 1234"
+ "test: int = 1234"
+ "test: int | None = 1234"
+ "test: List[Optional[int]] = [1234, None]")
+ :not ("if test == 1234:"
+ "_test = 1234"
+ "if (test := 3):"
+ "while test:"
+ "test['key'] = 1234"))
+
+ (:language "python" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "def\\s*JJJ\\b\\s*\\\("
+ :tests ("\tdef test(asdf)"
+ "def test()")
+ :not ("\tdef testnot(asdf)"
+ "def testnot()"))
+
+ (:language "python" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ\\b\\s*\\\(?"
+ :tests ("class test(object):"
+ "class test:")
+ :not ("class testnot:"
+ "class testnot(object):"))
+
+ (:language "python" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "async\\s+def\\s*JJJ\\b\\s*\\\("
+ :tests ("async def test(param)"
+ "async def test()")
+ :not ("async def testnot(param)"
+ "async def testnot()"))
+
+ (:language "python" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*JJJ\\s*=\\s*lambda\\b"
+ :tests ("test = lambda x: x"
+ "test = lambda: None")
+ :not ("testnot = lambda x: x"
+ "foo(test=lambda x: x)"))
+
+ ;;-- matlab
+ (:language "matlab" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*\\bJJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234")
+ :not ("for test = 1:2:"
+ "_test = 1234"))
+
+ (:language "matlab" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*function\\s*[^=]+\\s*=\\s*JJJ\\b"
+ :tests ("\tfunction y = test(asdf)"
+ "function x = test()"
+ "function [x, losses] = test(A, y, lambda, method, qtile)")
+ :not ("\tfunction testnot(asdf)"
+ "function testnot()"))
+
+ (:language "matlab" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*classdef\\s*JJJ\\b\\s*"
+ :tests ("classdef test")
+ :not ("classdef testnot"))
+
+ ;;-- nim
+ (:language "nim" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(const|let|var)\\s*JJJ\\*?\\s*(=|:)[^=:\\n]+"
+ :tests ("let test = 1234"
+ "var test = 1234"
+ "var test: Stat"
+ "const test = 1234"
+ "const test* = 1234")
+ :not ("if test == 1234:"))
+
+ (:language "nim" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(proc|func|macro|template)\\s*`?JJJ`?\\b\\*?\\s*\\\("
+ :tests ("\tproc test(asdf)"
+ "proc test()"
+ "func test()"
+ "macro test()"
+ "template test()"
+ "proc test*()")
+ :not ("\tproc testnot(asdf)"
+ "proc testnot()"))
+
+ (:language "nim" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "type\\s*JJJ\\b\\*?\\s*(\\{[^}]+\\})?\\s*=\\s*\\w+"
+ :tests ("type test = object"
+ "type test {.pure.} = enum"
+ "type test* = ref object")
+ :not ("type testnot = object"))
+
+ ;;-- nix
+ (:language "nix" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\b\\s*JJJ\\s*=[^=;]+"
+ :tests ("test = 1234;"
+ "test = 123;"
+ "test=123")
+ :not ("testNot = 1234;"
+ "Nottest = 1234;"
+ "AtestNot = 1234;"))
+
+ ;;-- org
+ ;; org named blocks
+ (:language "org" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*#\\+[nN][aA][mM][eE]:\\s*JJJ\\j"
+ :tests ("#+name: test"
+ "#+NAME: test"
+ "#+Name: test"
+ " #+name: test"
+ "#+name:test")
+ :not ("#+name: tester"
+ "#+name: test-block"
+ "some #+name: test"))
+
+ ;; org call references (#+call: block-name)
+ (:language "org" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*#\\+[cC][aA][lL][lL]:\\s*JJJ\\j"
+ :tests ("#+call: test"
+ "#+CALL: test"
+ "#+Call: test"
+ " #+call: test"
+ "#+call: test()"
+ "#+call: test(x=1)")
+ :not ("#+call: tester"
+ "#+call: test-other"
+ "some #+call: test"))
+
+ ;; org headings (* Heading text)
+ (:language "org" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\*+\\s+JJJ\\j"
+ :tests ("* test"
+ "** test"
+ "*** test"
+ "**** test"
+ "* test heading"
+ "** test with more words")
+ :not ("* tester"
+ "** testing"
+ "* test-other"
+ "not a * test heading"))
+
+ ;; org custom ID (:CUSTOM_ID: value in property drawers)
+ (:language "org" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*:CUSTOM_ID:\\s*JJJ\\j"
+ :tests (":CUSTOM_ID: test"
+ ":CUSTOM_ID: test"
+ " :CUSTOM_ID: test"
+ ":CUSTOM_ID:test")
+ :not (":CUSTOM_ID: tester"
+ ":CUSTOM_ID: test-other"
+ "inline :CUSTOM_ID: test text"))
+
+ ;;-- ruby
+ (:language "ruby" :type "variable"
+ :supports ("ag" "rg" "git-grep")
+ :regex "^\\s*((\\w+[.])*\\w+,\\s*)*JJJ(,\\s*(\\w+[.])*\\w+)*\\s*=([^=>~]|$)"
+ :tests ("test = 1234"
+ "self.foo, test, bar = args")
+ :not ("if test == 1234"
+ "foo_test = 1234"))
+
+ (:language "ruby" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])((private|public|protected)\\s+)?def\\s+(\\w+(::|[.]))*JJJ($|[^\\w|:])"
+ :tests ("def test(foo)"
+ "def test()"
+ "def test foo"
+ "def test; end"
+ "def self.test()"
+ "def MODULE::test()"
+ "private def test")
+ :not ("def test_foo"))
+
+ (:language "ruby" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|\\W)define(_singleton|_instance)?_method(\\s|[(])\\s*:JJJ($|[^\\w|:])"
+ :tests ("define_method(:test, &body)"
+ "mod.define_instance_method(:test) { body }"))
+
+ (:language "ruby" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])class\\s+(\\w*::)*JJJ($|[^\\w|:])"
+ :tests ("class test"
+ "class Foo::test"))
+
+ (:language "ruby" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])module\\s+(\\w*::)*JJJ($|[^\\w|:])"
+ :tests ("module test"
+ "module Foo::test"))
+
+ (:language "ruby" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|\\W)alias(_method)?\\W+JJJ(\\W|$)"
+ :tests ("alias test some_method"
+ "alias_method :test, :some_method"
+ "alias_method 'test' 'some_method'"
+ "some_class.send(:alias_method, :test, :some_method)")
+ :not ("alias some_method test"
+ "alias_method :some_method, :test"
+ "alias test_foo test"))
+
+ ;;-- Groovy
+ (:language "groovy" :type "variable"
+ :supports ("ag" "rg" "git-grep")
+ :regex "^\\s*((\\w+[.])*\\w+,\\s*)*JJJ(,\\s*(\\w+[.])*\\w+)*\\s*=([^=>~]|$)"
+ :tests ("test = 1234"
+ "self.foo, test, bar = args")
+ :not ("if test == 1234"
+ "foo_test = 1234"))
+
+ (:language "groovy" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])((private|public)\\s+)?def\\s+(\\w+(::|[.]))*JJJ($|[^\\w|:])"
+ :tests ("def test(foo)"
+ "def test()"
+ "def test foo"
+ "def test; end"
+ "def self.test()"
+ "def MODULE::test()"
+ "private def test")
+ :not ("def test_foo"))
+
+ (:language "groovy" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])class\\s+(\\w*::)*JJJ($|[^\\w|:])"
+ :tests ("class test"
+ "class Foo::test"))
+
+ ;;-- crystal
+ (:language "crystal" :type "variable"
+ :supports ("ag" "rg" "git-grep")
+ :regex "^\\s*((\\w+[.])*\\w+,\\s*)*JJJ(,\\s*(\\w+[.])*\\w+)*\\s*=([^=>~]|$)"
+ :tests ("test = 1234"
+ "self.foo, test, bar = args")
+ :not ("if test == 1234"
+ "foo_test = 1234"))
+
+ (:language "crystal" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])((private|public|protected)\\s+)?def\\s+(\\w+(::|[.]))*JJJ($|[^\\w|:])"
+ :tests ("def test(foo)"
+ "def test()"
+ "def test foo"
+ "def test; end"
+ "def self.test()"
+ "def MODULE::test()"
+ "private def test")
+ :not ("def test_foo"))
+
+ (:language "crystal" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])class\\s+(\\w*::)*JJJ($|[^\\w|:])"
+ :tests ("class test"
+ "class Foo::test"))
+
+ (:language "crystal" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])module\\s+(\\w*::)*JJJ($|[^\\w|:])"
+ :tests ("module test"
+ "module Foo::test"))
+
+ (:language "crystal" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])struct\\s+(\\w*::)*JJJ($|[^\\w|:])"
+ :tests ("struct test"
+ "struct Foo::test"))
+
+ (:language "crystal" :type "type"
+ :supports ("ag" "rg" "git-grep")
+ :regex "(^|[^\\w.])alias\\s+(\\w*::)*JJJ($|[^\\w|:])"
+ :tests ("alias test"
+ "alias Foo::test"))
+
+ ;;-- scad
+ (:language "scad" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234")
+ :not ("if test == 1234 {"))
+
+ (:language "scad" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*JJJ\\s*\\\("
+ :tests ("function test()"
+ "function test ()"))
+
+ (:language "scad" :type "module"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "module\\s*JJJ\\s*\\\("
+ :tests ("module test()"
+ "module test ()"))
+
+ ;;-- scala
+ (:language "scala" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bval\\s*JJJ\\s*=[^=\\n]+"
+ :tests ("val test = 1234")
+ :not ("case test => 1234"))
+
+ (:language "scala" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bvar\\s*JJJ\\s*=[^=\\n]+"
+ :tests ("var test = 1234")
+ :not ("case test => 1234"))
+
+ (:language "scala" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\btype\\s*JJJ\\s*=[^=\\n]+"
+ :tests ("type test = 1234")
+ :not ("case test => 1234"))
+
+ (:language "scala" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bdef\\s*JJJ\\s*\\\("
+ :tests ("def test(asdf)" "def test()"))
+
+ (:language "scala" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ\\s*\\\(?"
+ :tests ("class test(object)"))
+
+ (:language "scala" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "trait\\s*JJJ\\s*\\\(?"
+ :tests ("trait test(object)"))
+
+ (:language "scala" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "object\\s*JJJ\\s*\\\(?"
+ :tests ("object test(object)"))
+
+ ;;-- solidity
+ (:language "solidity" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*JJJ\\s*\\\("
+ :tests ("function test() internal"
+ "function test (uint x, address y)"
+ "function test() external"))
+
+ (:language "solidity" :type "modifier"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "modifier\\s*JJJ\\s*\\\("
+ :tests ("modifier test()"
+ "modifier test ()"))
+
+ (:language "solidity" :type "event"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "event\\s*JJJ\\s*\\\("
+ :tests ("event test();"
+ "event test (uint indexed x)"
+ "event test(uint x, address y)"))
+
+ (:language "solidity" :type "error"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "error\\s*JJJ\\s*\\\("
+ :tests ("error test();"
+ "error test (uint x)"
+ "error test(uint x, address y)"))
+
+ (:language "solidity" :type "contract"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "contract\\s*JJJ\\s*(is|\\\{)"
+ :tests ("contract test{"
+ "contract test {"
+ "contract test is foo"))
+
+ (:language "solidity" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "struct\\s*JJJ\\s*\\\{"
+ :tests ("struct test{"
+ "struct test {"))
+
+ ;;-- R
+ (:language "r" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*=[^=><]"
+ :tests ("test = 1234")
+ :not ("if (test == 1234)"))
+
+ (:language "r" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*<-\\s*function\\b"
+ :tests ("test <- function"
+ "test <- function(")
+ :not ("test <- functionX"))
+
+ ;;-- perl
+ (:language "perl" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "sub\\s*JJJ\\s*(\\{|\\()"
+ :tests ("sub test{"
+ "sub test {"
+ "sub test(" "sub test ("))
+
+ (:language "perl" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "JJJ\\s*=\\s*"
+ :tests ("$test = 1234"))
+
+ ;;-- Tcl
+ (:language "tcl" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "proc\\s+JJJ\\s*\\{"
+ :tests ("proc test{" "proc test {"))
+
+ (:language "tcl" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "set\\s+JJJ"
+ :tests ("set test 1234"))
+
+ (:language "tcl" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(variable|global)\\s+JJJ"
+ :tests ("variable test" "global test"))
+
+ ;;-- shell
+ (:language "shell" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*JJJ\\s*"
+ :tests ("function test{"
+ "function test {"
+ "function test () {")
+ :not ("function nottest {"))
+
+ (:language "shell" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "JJJ\\\(\\\)\\s*\\{"
+ :tests ("test() {")
+ :not ("testx() {"))
+
+ (:language "shell" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*=\\s*"
+ :tests ("test = 1234")
+ :not ("blahtest = 1234"))
+
+ ;;-- php
+ (:language "php" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*JJJ\\s*\\\("
+ :tests ("function test()"
+ "function test ()"))
+
+ (:language "php" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\*\\s@method\\s+[^ \t]+\\s+JJJ\\("
+ :tests ("/** @method string|false test($a)"
+ " * @method bool test()"))
+
+ (:language "php" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(\\s|->|\\$|::)JJJ\\s*=\\s*"
+ :tests ("$test = 1234"
+ "$foo->test = 1234"))
+
+ (:language "php" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\*\\s@property(-read|-write)?\\s+([^ \t]+\\s+)&?\\$JJJ(\\s+|$)"
+ :tests ("/** @property string $test"
+ "/** @property string $test description for $test property"
+ " * @property-read bool|bool $test"
+ " * @property-write \\ArrayObject<string,resource[]> $test"))
+
+ (:language "php" :type "trait"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "trait\\s*JJJ\\s*\\\{"
+ :tests ("trait test{"
+ "trait test {"))
+
+ (:language "php" :type "interface"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "interface\\s*JJJ\\s*(extends|\\\{)"
+ :tests ("interface test{"
+ "interface test {")
+ "interface test extends test2")
+
+ (:language "php" :type "class"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ\\s*(extends|implements|\\\{)"
+ :tests ("class test{"
+ "class test {"
+ "class test extends foo"
+ "class test implements foo"))
+
+ ;; PHP 8.1+ enum definition
+ (:language "php" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\benum\\s+JJJ\\b\\s*(:\\s*(int|string))?\\s*(implements[^{]+)?\\{"
+ :tests ("enum test {"
+ "enum test: int {"
+ "enum test: string {"
+ "enum test implements Stringable {"
+ "enum test: int implements JsonSerializable {")
+ :not ("// enum test" "$enum = 'test'"))
+
+ ;; PHP 8.1+ enum case
+ (:language "php" :type "constant"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*case\\s+JJJ\\s*(;|=)"
+ :tests (" case test;"
+ "case test = 1;"
+ "case test = 'value';")
+ :not ("case 'test':" "case \"test\":" "default:"))
+
+ ;; PHP abstract class
+ (:language "php" :type "class"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\babstract\\s+class\\s+JJJ\\s*(extends|implements|\\\{)"
+ :tests ("abstract class test {"
+ "abstract class test extends Base {"
+ "abstract class test implements Foo {")
+ :not ("// abstract class test"))
+
+ ;; PHP final class (including final readonly)
+ (:language "php" :type "class"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bfinal\\s+(readonly\\s+)?class\\s+JJJ\\s*(extends|implements|\\\{)"
+ :tests ("final class test {"
+ "final readonly class test {"
+ "final class test extends Base {")
+ :not ("// final class test"))
+
+ ;; PHP readonly class (including readonly final)
+ (:language "php" :type "class"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\breadonly\\s+(final\\s+)?class\\s+JJJ\\s*(extends|implements|\\\{)"
+ :tests ("readonly class test {"
+ "readonly final class test {"
+ "readonly class test implements Serializable {")
+ :not ("// readonly class test"))
+
+ ;; PHP class constant with optional visibility/final
+ (:language "php" :type "constant"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(final\\s+)?(public|protected|private)?\\s*const\\s+JJJ\\s*="
+ :tests ("const test = 'value';"
+ "public const test = 100;"
+ "private const test = 'secret';"
+ "protected const test = [];"
+ "final public const test = 42;"
+ "final const test = true;")
+ :not ("$const = 'test'" "// const test"))
+
+ ;; PHP global const (outside class)
+ (:language "php" :type "constant"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^const\\s+JJJ\\s*="
+ :tests ("const test = 'app_name';"
+ "const test = 42;"
+ "const test = [];")
+ :not (" const test =" "public const test ="))
+
+ ;; PHP define() function
+ (:language "php" :type "constant"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*define\\s*\\(\\s*['\"]JJJ['\"]"
+ :tests ("define('test', 100);"
+ "define(\"test\", 'value');"
+ "define('test',")
+ :not ("// define('test'" "$define = 'test'"))
+
+ ;; PHP typed property declaration
+ (:language "php" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(public|protected|private)\\s+(static\\s+)?(readonly\\s+)?\\??[A-Za-z_][A-Za-z0-9_\\\\|&]*\\s+\\$JJJ\\s*(;|=|,)"
+ :tests ("public int $test;"
+ "private string $test = '';"
+ "protected ?array $test = null;"
+ "public readonly string $test;"
+ "private static int $test = 0;"
+ "public DateTime $test;")
+ :not ("public function foo(int $test)" "return $test;"))
+
+ ;; PHP static property (without explicit type)
+ (:language "php" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(public|protected|private)?\\s*static\\s+\\$JJJ\\s*(;|=)"
+ :tests ("static $test;"
+ "public static $test = [];"
+ "private static $test = null;")
+ :not ("static function test()" "static::$test"))
+
+ ;; PHP namespace declaration
+ (:language "php" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bnamespace\\s+([A-Za-z_][A-Za-z0-9_]*\\\\)*JJJ\\s*[;{]"
+ :tests ("namespace test;"
+ "namespace App\\test;"
+ "namespace App\\Services\\test {")
+ :not ("// namespace test" "use App\\test;"))
+
+ ;; PHP use statement for class imports
+ (:language "php" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\buse\\s+([A-Za-z_][A-Za-z0-9_]*\\\\)*JJJ\\s*(;|,|\\s+as\\s+)"
+ :tests ("use test;"
+ "use App\\test;"
+ "use App\\Models\\test as TestModel;"
+ "use App\\test, App\\Other;")
+ :not ("use function test;" "use const test;"))
+
+ ;;-- dart
+ (:language "dart" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*\\([^()]*\\)\\s*[{]"
+ :tests ("test(foo) {"
+ "test (foo){"
+ "test(foo){"))
+
+ (:language "dart" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ\\s*[\\\(\\\{]"
+ :tests ("class test(object) {"
+ "class test{"))
+
+ ;;-- dlang
+ (:language "dlang" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^[[:space:]]*([][[:alnum:]_!.*<>]+[[:space:]]+)+JJJ[[:space:]]*\\\([^;{}]*\\\)([[:space:]]+@?[[:alnum:]_]+)*[[:space:]]*[{]"
+ :tests ("void test() {"
+ "public static int test(int value) {"
+ "auto test(T)(T value) {"
+ "void test() pure {"
+ "void test() @safe {")
+ :not ("return test();"
+ "auto value = test(arg);"
+ "test();"))
+
+ (:language "dlang" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n)]+"
+ :tests ("int test = 1234;"
+ "immutable test = 1;"
+ "auto test = foo();")
+ :not ("if (test == 1234)"
+ "int nottest = 44;"))
+
+ (:language "dlang" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(class|interface|struct|union|enum|alias)\\s+JJJ\\b"
+ :tests ("class test {"
+ "struct test {"
+ "alias test = int;")
+ :not ("class testnot {"
+ "alias nottest = test;"))
+
+ ;;-- faust
+ (:language "faust" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\(\\\(.+\\\)\)*\\s*="
+ :tests ("test = osc + 0.5;"
+ "test(freq) = osc(freq) + 0.5;"))
+
+ ;;-- fennel
+ (:language "fennel" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\((local|var)\\s+JJJ\\j"
+ :tests ("(local test (foo)"
+ "(var test (foo)"))
+
+ (:language "fennel" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(fn\\s+JJJ\\j"
+ :tests ("(fn test [foo]")
+ :not ("(fn test? [foo]"))
+
+ (:language "fennel" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\(macro\\s+JJJ\\j"
+ :tests ("(macro test [foo]"))
+
+ ;;-- fortran
+ (:language "fortran" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234")
+ :not ("if (test == 1234)"))
+
+ (:language "fortran" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\b(function|subroutine|FUNCTION|SUBROUTINE)\\s+JJJ\\b\\s*\\\("
+ :tests ("function test (foo)"
+ "integer function test(foo)"
+ "subroutine test (foo, bar)"
+ "FUNCTION test (foo)"
+ "INTEGER FUNCTION test(foo)"
+ "SUBROUTINE test (foo, bar)")
+ :not ("end function test"
+ "end subroutine test"
+ "END FUNCTION test"
+ "END SUBROUTINE test"))
+
+ (:language "fortran" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*(interface|INTERFACE)\\s+JJJ\\b"
+ :tests ("interface test"
+ "INTERFACE test")
+ :not ("interface test2"
+ "end interface test"
+ "INTERFACE test2"
+ "END INTERFACE test"))
+
+ (:language "fortran" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*(module|MODULE)\\s+JJJ\\s*"
+ :tests ("module test"
+ "MODULE test")
+ :not ("end module test"
+ "END MODULE test"))
+
+ ;;-- go
+ (:language "go" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234")
+ :not ("if test == 1234 {"))
+
+ (:language "go" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*:=\\s*"
+ :tests ("test := 1234"))
+
+ (:language "go" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "func\\s+\\\([^\\\)]*\\\)\\s+JJJ\\s*\\\("
+ :tests ("func (s *blah) test(filename string) string {"))
+
+ (:language "go" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "func\\s+JJJ\\s*\\\("
+ :tests ("func test(url string) (string, error)"))
+
+ (:language "go" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "type\\s+JJJ\\s+struct\\s+\\\{"
+ :tests ("type test struct {"))
+
+ (:language "go" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "type\\s+JJJ(\\\[[^]]+\\\])?\\s+interface\\s*\\\{"
+ :tests ("type test interface {"
+ "type test[T comparable] interface {")
+ :not ("type testnot interface {"))
+
+ (:language "go" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "type\\s+JJJ(\\\[[^]]+\\\])?\\s+"
+ :tests ("type test int"
+ "type test[T any] []byte"
+ "type test = string"
+ "type test []byte"
+ "type test string")
+ :not ("type testnot int"))
+
+ ;;-- javascript extended
+ (:language "javascript" :tags ("angular") :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(service|factory)\\\(['\"]JJJ['\"]"
+ :tests ("module.factory('test', [\"$rootScope\", function($rootScope) {"))
+
+ (:language "javascript" :tags ("es6") :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*[=:]\\s*\\\([^\\\)]*\\\)\\s+=>"
+ :tests ("const test = (foo) => " "test: (foo) => {" " test: (foo) => {"))
+
+ (:language "javascript" :tags ("es6") :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*\\([^()]*\\)\\s*[{]"
+ :tests ("test(foo) {" "test (foo){" "test(foo){")
+ :not ("test = blah.then(function(){"))
+
+ (:language "javascript" :tags ("es6") :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ\\s*[\\\(\\\{]"
+ :tests ("class test(object) {" "class test{"))
+
+ (:language "javascript" :tags ("es6") :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ\\s+extends"
+ :tests ("class test extends Component{"))
+
+ ;;-- javascript
+ (:language "javascript" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234"
+ "const test = props =>")
+ :not ("if (test === 1234)"))
+
+ (:language "javascript" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bfunction\\b[^\\(]*\\\(\\s*[^\\)]*\\bJJJ\\b\\s*,?\\s*\\\)?"
+ :tests ("function (test)"
+ "function (test, blah)"
+ "function somefunc(test, blah) {"
+ "function(blah, test)")
+ :not ("function (testLen)"
+ "function (test1, blah)"
+ "function somefunc(testFirst, blah) {"
+ "function(blah, testLast)"
+ "function (Lentest)"
+ "function (blahtest, blah)"
+ "function somefunc(Firsttest, blah) {"
+ "function(blah, Lasttest)"))
+
+ (:language "javascript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*JJJ\\s*\\\("
+ :tests ("function test()"
+ "function test ()"))
+
+ (:language "javascript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*:\\s*function\\s*\\\("
+ :tests ("test: function()"))
+
+ (:language "javascript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*=\\s*function\\s*\\\("
+ :tests ("test = function()"))
+
+ ;;-- hcl terraform
+ (:language "hcl" :type "block"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(variable|output|module)\\s*\"JJJ\"\\s*\\\{"
+ :tests ("variable \"test\" {"
+ "output \"test\" {"
+ "module \"test\" {"))
+
+ (:language "hcl" :type "block"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(data|resource)\\s*\"\\w+\"\\s*\"JJJ\"\\s*\\\{"
+ :tests ("data \"openstack_images_image_v2\" \"test\" {"
+ "resource \"google_compute_instance\" \"test\" {"))
+
+ ;;-- typescript
+ (:language "typescript" :tags ("angular") :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(service|factory)\\\(['\"]JJJ['\"]"
+ :tests ("module.factory('test', [\"$rootScope\", function($rootScope) {"))
+
+ (:language "typescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*[=:]\\s*\\\([^\\\)]*\\\)\\s+=>"
+ :tests ("const test = (foo) => "
+ "test: (foo) => {"
+ " test: (foo) => {"))
+
+ (:language "typescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*\\([^()]*\\)\\s*[{]"
+ :tests ("test(foo) {"
+ "test (foo){"
+ "test(foo){")
+ :not ("test = blah.then(function(){"))
+
+ (:language "typescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ(\\s*<[^>]*>)?\\s*[\\\(\\\{]"
+ :tests ("class test{"
+ "class test<T>{"))
+
+ (:language "typescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ(\\s*<[^>]*>)?\\s+extends"
+ :tests ("class test extends Component{"
+ "class test<T> extends Component{"))
+
+ (:language "typescript" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ(\\s*<[^>]*>)?\\s*[\\\(\\\{]"
+ :tests ("class test{"
+ "class test<T>{"))
+
+ (:language "typescript" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "class\\s*JJJ(\\s*<[^>]*>)?\\s+extends"
+ :tests ("class test extends Component{"
+ "class test<T> extends Component{"))
+
+ (:language "typescript" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(export\\s+)?interface\\s+JJJ\\b"
+ :tests ("interface test{"
+ "interface test extends Thing{"
+ "export interface test{")
+ :not ("interface testnot{"))
+
+ (:language "typescript" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(export\\s+)?type\\s+JJJ\\b"
+ :tests ("type test = number"
+ "type test<T> = T"
+ "export type test = number")
+ :not ("type testnot = number"))
+
+ (:language "typescript" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(export\\s+)?enum\\s+JJJ\\b"
+ :tests ("enum test{"
+ "export enum test{")
+ :not ("enum testnot{"))
+
+ (:language "typescript" :type "module"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(declare\\s+)?namespace\\s+JJJ\\b"
+ :tests ("declare namespace test"
+ "namespace test")
+ :not ("declare testnot"))
+
+ (:language "typescript" :type "module"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(export\\s+)?module\\s+JJJ\\b"
+ :tests ("export module test"
+ "module test")
+ :not ("module testnot"))
+
+ (:language "typescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*JJJ\\s*\\\("
+ :tests ("function test()"
+ "function test ()"))
+
+ (:language "typescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*:\\s*function\\s*\\\("
+ :tests ("test: function()"))
+
+ (:language "typescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*=\\s*function\\s*\\\("
+ :tests ("test = function()"))
+
+ (:language "typescript" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234" "const test = props =>")
+ :not ("if (test === 1234)"))
+
+ (:language "typescript" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bfunction\\b[^\\(]*\\\(\\s*[^\\)]*\\bJJJ\\b\\s*,?\\s*\\\)?"
+ :tests ("function (test)"
+ "function (test, blah)"
+ "function somefunc(test, blah) {"
+ "function(blah, test)")
+ :not ("function (testLen)"
+ "function (test1, blah)"
+ "function somefunc(testFirst, blah) {"
+ "function(blah, testLast)"
+ "function (Lentest)"
+ "function (blahtest, blah)"
+ "function somefunc(Firsttest, blah) {"
+ "function(blah, Lasttest)"))
+
+ ;;-- julia
+ (:language "julia" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(@noinline|@inline)?\\s*function\\s*JJJ(\\{[^\\}]*\\})?\\("
+ :tests ("function test()"
+ "@inline function test()"
+ "function test{T}(h)"))
+
+ (:language "julia" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(@noinline|@inline)?JJJ(\\{[^\\}]*\\})?\\([^\\)]*\\)\s*="
+ :tests ("test(a)=1"
+ "test(a,b)=1*8"
+ "@noinline test()=1"
+ "test{T}(x)=x"))
+
+ (:language "julia" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "macro\\s*JJJ\\("
+ :tests ("macro test(a)=1"
+ " macro test(a,b)=1*8"))
+
+ (:language "julia" :type "variable"
+ :supports ("ag" "rg")
+ :regex "const\\s+JJJ\\b"
+ :tests ("const test = "))
+
+ (:language "julia" :type "type"
+ :supports ("ag" "rg")
+ :regex "(mutable)?\\s*struct\\s*JJJ"
+ :tests ("struct test"))
+
+ (:language "julia" :type "type"
+ :supports ("ag" "rg")
+ :regex "(type|immutable|abstract)\\s*JJJ"
+ :tests ("type test"
+ "immutable test"
+ "abstract test <:Testable" ))
+
+ ;;-- haskell
+ (:language "haskell" :type "module"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "^module\\s+JJJ\\s+"
+ :tests ("module test (exportA, exportB) where")
+ :not ("-- module test" "import test"))
+
+ ; TODO Doesn't support any '=' in arguments. E.g. 'foo A{a = b,..} = bar'.
+ (:language "haskell" :type "top level function"
+ :supports ("ag" "rg")
+ :regex "^\\bJJJ(?!(\\s+::))\\s+((.|\\s)*?)=\\s+"
+ :tests ("test n = n * 2"
+ "test X{..} (Y a b c) \n bcd \n =\n x * y"
+ "test ab cd e@Datatype {..} (Another thing, inTheRow) = \n undefined"
+ "test = runRealBasedMode @ext @ctx identity identity"
+ "test unwrap wrap nr@Naoeu {..} (Action action, specSpecs) = \n undefined")
+ :not ("nottest n = n * 2"
+ "let testnot x y = x * y"
+ "test $ y z"
+ "let test a o = mda"
+ "test :: Sometype -> AnotherType aoeu kek = undefined"))
+
+ (:language "haskell" :type "type-like"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "^\\s*((data(\\s+family)?)|(newtype)|(type(\\s+family)?))\\s+JJJ\\s+"
+ :tests ("newtype test a = Something { b :: Kek }"
+ "data test a b = Somecase a | Othercase b"
+ "type family test (x :: *) (xs :: [*]) :: Nat where"
+ "data family test "
+ "type test = TestAlias")
+ :not ("newtype nottest a = nottest (Not a)"
+ "data testnot b = Aoeu"))
+
+ ; datatype contstuctor that doesn't match type definition.
+ (:language "haskell" :type "(data)type constructor 1"
+ :supports ("ag" "rg")
+ :regex "(data|newtype)\\s{1,3}(?!JJJ\\s+)([^=]{1,40})=((\\s{0,3}JJJ\\s+)|([^=]{0,500}?((?<!(-- ))\\|\\s{0,3}JJJ\\s+)))"
+ :tests ("data Something a = test { b :: Kek }"
+ "data Mem a = TrueMem { b :: Kek } | test (Mem Int) deriving Mda"
+ "newtype SafeTest a = test (Kek a) deriving (YonedaEmbedding)")
+ :not ("data test = test { b :: Kek }"))
+
+
+ (:language "haskell" :type "data/newtype record field"
+ :supports ("ag" "rg")
+ :regex "(data|newtype)([^=]*)=[^=]*?({([^=}]*?)(\\bJJJ)\\s+::[^=}]+})"
+ :tests ("data Mem = Mem { \n mda :: A \n , test :: Kek \n , \n aoeu :: E \n }"
+ "data Mem = Mem { \n test :: A \n , mda :: Kek \n , \n aoeu :: E \n }"
+ "data Mem = Mem { \n mda :: A \n , aoeu :: Kek \n , \n test :: E \n }"
+ "data Mem = Mem { test :: Kek } deriving Mda"
+ "data Mem = Mem { \n test :: Kek \n } deriving Mda"
+ "newtype Mem = Mem { \n test :: Kek \n } deriving (Eq)"
+ "newtype Mem = Mem { -- | Some docs \n test :: Kek -- ^ More docs } deriving Eq"
+ "newtype Mem = Mem { test :: Kek } deriving (Eq,Monad)"
+ "newtype NewMem = OldMem { test :: [Tx] }"
+ "newtype BlockHeaderList ssc = BHL\n { test :: ([Aoeu a], [Ssss])\n } deriving (Eq)")
+ :not ("data Heh = Mda { sometest :: Kek, testsome :: Mem }"))
+
+ (:language "haskell" :type "typeclass"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "^class\\s+(.+=>\\s*)?JJJ\\s+"
+ :tests (
+ "class (Constr1 m, Constr 2) => test (Kek a) where"
+ "class test (Veryovka a) where ")
+ :not ("class test2 (Kek a) where"
+ "class maketest (AoeuTest x y z) where"))
+
+ ;;-- ocaml
+ (:language "ocaml" :type "type"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "^\\s*(and|type)\\s+.*\\bJJJ\\b"
+ :tests ("type test ="
+ "and test ="
+ "type 'a test ="
+ "type ('a, _, 'c) test"))
+
+ (:language "ocaml" :type "variable"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "let\\s+JJJ\\b"
+ :tests ("let test ="
+ "let test x y ="))
+
+ (:language "ocaml" :type "variable"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "let\\s+rec\\s+JJJ\\b"
+ :tests ("let rec test ="
+ "let rec test x y ="))
+
+ (:language "ocaml" :type "variable"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "\\s*val\\s*\\bJJJ\\b\\s*"
+ :tests ("val test"))
+
+ (:language "ocaml" :type "module"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "^\\s*module\\s*\\bJJJ\\b"
+ :tests ("module test ="))
+
+ (:language "ocaml" :type "module"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "^\\s*module\\s*type\\s*\\bJJJ\\b"
+ :tests ("module type test ="))
+
+ ;;-- purescript
+ (:language "purescript" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*\\bJJJ\\b\\s+(::|[^=]*\\s=[^=])"
+ :tests ("test :: Int -> String"
+ "test :: Number -> Number -> Number"
+ " test :: Int -> String"
+ "test x y = x + y"
+ "test (Just x) = x"
+ "test { x } = x")
+ :not ("nottest :: Int -> String"
+ "testing x = 1"
+ "test x == y"
+ "test >>= bar"))
+
+ (:language "purescript" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*\\bJJJ\\s*=[^=]"
+ :tests ("test = 42"
+ "test = \\x -> x + 1"
+ " test = compute x")
+ :not ("test == 42"
+ "-- test = 42"))
+
+ (:language "purescript" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\blet\\s+JJJ\\s*(\\::|=[^=])"
+ :tests ("let test = 42"
+ "let test :: Int")
+ :not ("let testing = 42"
+ "let test == 42"))
+
+ (:language "purescript" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^\\s*(type|newtype|data)\\s+JJJ\\b"
+ :tests ("type test = Int"
+ "newtype test = test Int"
+ "data test = test Int | Other String")
+ :not ("type testOther = Int"
+ "data testOther = testOther Int"))
+
+ ;; lua
+ (:language "lua" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234")
+ :not ("if test === 1234"))
+
+ (:language "lua" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bfunction\\b[^\\(]*\\\(\\s*[^\\)]*\\bJJJ\\b\\s*,?\\s*\\\)?"
+ :tests ("function (test)"
+ "function (test, blah)"
+ "function somefunc(test, blah)"
+ "function(blah, test)")
+ :not ("function (testLen)"
+ "function (test1, blah)"
+ "function somefunc(testFirst, blah)"
+ "function(blah, testLast)"
+ "function (Lentest)"
+ "function (blahtest, blah)"
+ "function somefunc(Firsttest, blah)"
+ "function(blah, Lasttest)"))
+
+ (:language "lua" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*JJJ\\s*\\\("
+ :tests ("function test()" "function test ()"))
+
+ (:language "lua" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*.+[.:]JJJ\\s*\\\("
+ :tests ("function MyClass.test()"
+ "function MyClass.test ()"
+ "function MyClass:test()"
+ "function MyClass:test ()"))
+
+ (:language "lua" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*=\\s*function\\s*\\\("
+ :tests ("test = function()"))
+
+ (:language "lua" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\b.+\\.JJJ\\s*=\\s*function\\s*\\\("
+ :tests ("MyClass.test = function()"))
+
+ ;;-- rust
+ (:language "rust" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\blet\\s+(\\\([^=\\n]*)?(mut\s+)?JJJ([^=\\n]*\\\))?(:\\s*[^=\\n]+)?\\s*=\\s*[^=\\n]+"
+ :tests ("let test = 1234;"
+ "let test: u32 = 1234;"
+ "let test: Vec<u32> = Vec::new();"
+ "let mut test = 1234;"
+ "let mut test: Vec<u32> = Vec::new();"
+ "let (a, test, b) = (1, 2, 3);"
+ "let (a, mut test, mut b) = (1, 2, 3);"
+ "let (mut a, mut test): (u32, usize) = (1, 2);"))
+
+ (:language "rust" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bconst\\s+JJJ:\\s*[^=\\n]+\\s*=[^=\\n]+"
+ :tests ("const test: u32 = 1234;"))
+
+ (:language "rust" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bstatic\\s+(mut\\s+)?JJJ:\\s*[^=\\n]+\\s*=[^=\\n]+"
+ :tests ("static test: u32 = 1234;"
+ "static mut test: u32 = 1234;"))
+
+ ;; variable in method signature
+ (:language "rust" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bfn\\s+.+\\s*\\\((.+,\\s+)?JJJ:\\s*[^=\\n]+\\s*(,\\s*.+)*\\\)"
+ :tests ("fn abc(test: u32) -> u32 {"
+ "fn abc(x: u32, y: u32, test: Vec<u32>, z: Vec<Foo>)"
+ "fn abc(x: u32, y: u32, test: &mut Vec<u32>, z: Vec<Foo>)"))
+
+ ;; "if let" and "while let" desugaring
+ (:language "rust" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(if|while)\\s+let\\s+([^=\\n]+)?(mut\\s+)?JJJ([^=\\n\\\(]+)?\\s*=\\s*[^=\\n]+"
+ :tests ("if let Some(test) = abc() {"
+ "if let Some(mut test) = abc() {"
+ "if let Ok(test) = abc() {"
+ "if let Ok(mut test) = abc() {"
+ "if let Foo(mut test) = foo {"
+ "if let test = abc() {"
+ "if let Some(test) = abc()"
+ "if let Some((a, test, b)) = abc()"
+ "while let Some(test) = abc() {"
+ "while let Some(mut test) = abc() {"
+ "while let Ok(test) = abc() {"
+ "while let Ok(mut test) = abc() {")
+ :not ("while let test(foo) = abc() {"))
+
+ ;; structure fields
+ (:language "rust" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "struct\\s+[^\\n{]+[{][^}]*(\\s*JJJ\\s*:\\s*[^\\n},]+)[^}]*}"
+ :tests ("struct Foo { abc: u32, test: Vec<String>, b: PathBuf }"
+ "struct Foo<T>{test:Vec<T>}"
+ "struct FooBar<'a> { test: Vec<String> }")
+ :not ("struct Foo { abc: u32, b: Vec<String> }"
+ "/// ... construct the equivalent ...\nfn abc() {\n"))
+
+ ;; enum variants
+ (:language "rust" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "enum\\s+[^\\n{]+\\s*[{][^}]*\\bJJJ\\b[^}]*}"
+ :tests ("enum Foo { VariantA, test, VariantB(u32) }"
+ "enum Foo<T> { test(T) }"
+ "enum BadStyle{test}"
+ "enum Foo32 { Bar, testing, test(u8) }")
+ :not ("enum Foo { testing }"))
+
+ (:language "rust" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bfn\\s+JJJ\\s*\\\("
+ :tests ("fn test(asdf: u32)"
+ "fn test()"
+ "pub fn test()"))
+
+ (:language "rust" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bmacro_rules!\\s+JJJ"
+ :tests ("macro_rules! test"))
+
+ (:language "rust" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "struct\\s+JJJ\\s*[{\\\(]?"
+ :tests ("struct test(u32, u32)"
+ "struct test;"
+ "struct test { abc: u32, def: Vec<String> }"))
+
+ (:language "rust" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "trait\\s+JJJ\\s*[{]?"
+ :tests ("trait test;" "trait test { fn abc() -> u32; }"))
+
+ (:language "rust" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\btype\\s+JJJ([^=\\n]+)?\\s*=[^=\\n]+;"
+ :tests ("type test<T> = Rc<RefCell<T>>;"
+ "type test = Arc<RwLock<Vec<u32>>>;"))
+
+ (:language "rust" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "impl\\s+((\\w+::)*\\w+\\s+for\\s+)?(\\w+::)*JJJ\\s+[{]?"
+ :tests ("impl test {"
+ "impl abc::test {"
+ "impl std::io::Read for test {"
+ "impl std::io::Read for abc::test {"))
+
+ (:language "rust" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "mod\\s+JJJ\\s*[{]?"
+ :tests ("mod test;" "pub mod test {"))
+
+ ;; enum definitions
+ (:language "rust" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\benum\\s+JJJ\\b\\s*[{]?"
+ :tests ("enum test { A, B, C }"
+ "enum test {"
+ "pub enum test<T> {"
+ "pub enum test<T> { A, B }"
+ "pub enum test<T: Clone + Debug> {"
+ "enum test { Variant1, Variant2(Type) }"
+ "pub(crate) enum test { Variant }")
+ :not ("enum testing { A, B }"
+ "enum Foo { test, Bar }"))
+
+ ;;-- elixir
+ (:language "elixir" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bdef(p)?\\s+JJJ\\s*[ ,\\\(]"
+ :tests ("def test do"
+ "def test, do:"
+ "def test() do"
+ "def test(), do:"
+ "def test(foo, bar) do"
+ "def test(foo, bar), do:"
+ "defp test do"
+ "defp test(), do:"))
+
+ (:language "elixir" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*JJJ\\s*=[^=\\n]+"
+ :tests ("test = 1234")
+ :not ("if test == 1234"))
+
+ (:language "elixir" :type "module"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "defmodule\\s+(\\w+\\.)*JJJ\\s+"
+ :tests ("defmodule test do"
+ "defmodule Foo.Bar.test do"))
+
+ (:language "elixir" :type "module"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "defprotocol\\s+(\\w+\\.)*JJJ\\s+"
+ :tests ("defprotocol test do"
+ "defprotocol Foo.Bar.test do"))
+
+ ;;-- erlang
+ (:language "erlang" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^JJJ\\b\\s*\\\("
+ :tests ("test() ->"
+ "test()->"
+ "test(Foo) ->"
+ "test (Foo,Bar) ->"
+ "test(Foo, Bar)->"))
+
+ (:language "erlang" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*JJJ\\s*=[^:=\\n]+"
+ :tests ("test = 1234")
+ :not ("if test =:= 1234"
+ "if test == 1234"))
+
+ (:language "erlang" :type "module"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^-module\\\(JJJ\\\)"
+ :tests ("-module(test)."))
+
+ ;;-- scss
+ (:language "scss" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "@mixin\\sJJJ\\b\\s*\\\("
+ :tests ("@mixin test()"))
+
+ (:language "scss" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "@function\\sJJJ\\b\\s*\\\("
+ :tests ("@function test()"))
+
+ (:language "scss" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "JJJ\\s*:\\s*"
+ :tests ("test :"))
+
+ ;;-- sml
+ (:language "sml" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*(data)?type\\s+.*\\bJJJ\\b"
+ :tests ("datatype test ="
+ "datatype test="
+ "datatype 'a test ="
+ "type test ="
+ "type 'a test ="
+ "type 'a test"
+ "type test")
+ :not ("datatypetest ="))
+
+ (:language "sml" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*val\\s+\\bJJJ\\b"
+ :tests ("val test ="
+ "val test="
+ "val test : bool"))
+
+ (:language "sml" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*fun\\s+\\bJJJ\\b.*\\s*="
+ :tests ("fun test list ="
+ "fun test (STRING_NIL, a) ="
+ "fun test ((s1,s2): 'a queue) : 'a * 'a queue ="
+ "fun test (var : q) : int ="
+ "fun test f e xs ="))
+
+ (:language "sml" :type "module"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*(structure|signature|functor)\\s+\\bJJJ\\b"
+ :tests ("structure test ="
+ "structure test : MYTEST ="
+ "signature test ="
+ "functor test (T:TEST) ="
+ "functor test(T:TEST) ="))
+
+ ;;-- sql
+ (:language "sql" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(CREATE|create)\\s+(.+?\\s+)?(FUNCTION|function|PROCEDURE|procedure)\\s+JJJ\\s*\\\("
+ :tests ("CREATE FUNCTION test(i INT) RETURNS INT"
+ "create or replace function test (int)"
+ "CREATE PROCEDURE test (OUT p INT)"
+ "create definer = 'test'@'localhost' procedure test()"))
+
+ (:language "sql" :type "table"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(CREATE|create)\\s+(.+?\\s+)?(TABLE|table)(\\s+(IF NOT EXISTS|if not exists))?\\s+JJJ\\b"
+ :tests ("CREATE TABLE test ("
+ "create temporary table if not exists test"
+ "CREATE TABLE IF NOT EXISTS test ("
+ "create global temporary table test"))
+
+ (:language "sql" :type "view"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(CREATE|create)\\s+(.+?\\s+)?(VIEW|view)\\s+JJJ\\b"
+ :tests ("CREATE VIEW test ("
+ "create sql security definer view test"
+ "CREATE OR REPLACE VIEW test AS foo"))
+
+ (:language "sql" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(CREATE|create)\\s+(.+?\\s+)?(TYPE|type)\\s+JJJ\\b"
+ :tests ("CREATE TYPE test"
+ "CREATE OR REPLACE TYPE test AS foo ("
+ "create type test as ("))
+
+ ;;-- systemverilog
+ (:language "systemverilog" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*class\\s+\\bJJJ\\b"
+ :tests ("virtual class test;"
+ "class test;"
+ "class test extends some_class")
+ :not ("virtual class testing;"
+ "class test2;"
+ "class some_test"
+ "class some_class extends test"))
+
+ (:language "systemverilog" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*task\\s+\\bJJJ\\b"
+ :tests ("task test ("
+ "task test(")
+ :not ("task testing ("
+ "task test2("))
+
+ (:language "systemverilog" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\b\\s*="
+ :tests ("assign test ="
+ "assign test="
+ "int test ="
+ "int test=")
+ :not ("assign testing ="
+ "assign test2="))
+
+ (:language "systemverilog" :type "function" :
+ supports ("ag" "rg" "git-grep")
+ :regex "function\\s[^\\s]+\\s*\\bJJJ\\b"
+ :tests ("function Matrix test ;"
+ "function Matrix test;")
+ :not ("function test blah"))
+
+ ;; matches SV class handle declarations
+ (:language "systemverilog" :type "function"
+ :supports ("ag" "rg" "git-grep")
+ :regex "^\\s*[^\\s]*\\s*[^\\s]+\\s+\\bJJJ\\b"
+ :tests ("some_class_name test"
+ " another_class_name test ;"
+ "some_class test[];"
+ "some_class #(1) test")
+ :not ("test some_class_name"
+ "class some_class extends test"))
+
+ ;;-- vhdl
+ (:language "vhdl" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*type\\s+\\bJJJ\\b"
+ :tests ("type test is"
+ "type test is")
+ :not ("type testing is"
+ "type test2 is"))
+
+ (:language "vhdl" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*constant\\s+\\bJJJ\\b"
+ :tests ("constant test :"
+ "constant test:")
+ :not ("constant testing "
+ "constant test2:"))
+
+ (:language "vhdl" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "function\\s*\"?JJJ\"?\\s*\\\("
+ :tests ("function test(signal)"
+ "function test (signal)"
+ "function \"test\" (signal)")
+ :not ("function testing(signal"))
+
+ ;;-- latex
+ (:language "tex" :type "command"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\\.*newcommand\\\*?\\s*\\\{\\s*(\\\\)JJJ\\s*}"
+ :tests ("\\newcommand{\\test}"
+ "\\renewcommand{\\test}"
+ "\\renewcommand*{\\test}"
+ "\\newcommand*{\\test}"
+ "\\renewcommand{ \\test }")
+ :not("\\test" "test"))
+
+ (:language "tex" :type "command"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\\.*newcommand\\\*?\\s*(\\\\)JJJ\\j"
+ :tests ("\\newcommand\\test {}"
+ "\\renewcommand\\test{}"
+ "\\newcommand \\test")
+ :not("\\test"
+ "test"))
+
+ (:language "tex" :type "length" :
+ supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\\(s)etlength\\s*\\\{\\s*(\\\\)JJJ\\s*}"
+ :tests ("\\setlength { \\test}"
+ "\\setlength{\\test}"
+ "\\setlength{\\test}{morecommands}" )
+ :not("\\test"
+ "test"))
+
+ (:language "tex" :type "counter"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\\newcounter\\\{\\s*JJJ\\s*}"
+ :tests ("\\newcounter{test}" )
+ :not("\\test"
+ "test"))
+
+ (:language "tex" :type "environment"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\\\.*newenvironment\\s*\\\{\\s*JJJ\\s*}"
+ :tests ("\\newenvironment{test}"
+ "\\newenvironment {test}{morecommands}"
+ "\\lstnewenvironment{test}"
+ "\\newenvironment {test}" )
+ :not("\\test"
+ "test" ))
+
+ ;;-- pascal (todo: var, type, const)
+ (:language "pascal" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bfunction\\s+JJJ\\b"
+ :tests (" function test : "))
+
+ (:language "pascal" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bprocedure\\s+JJJ\\b"
+ :tests (" procedure test ; "))
+
+ ;;-- f#
+ (:language "fsharp" :type "variable"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "let\\s+JJJ\\b.*\\\="
+ :tests ("let test = 1234"
+ "let test() = 1234"
+ "let test abc def = 1234")
+ :not ("let testnot = 1234"
+ "let testnot() = 1234"
+ "let testnot abc def = 1234"))
+
+ (:language "fsharp" :type "interface"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "member(\\b.+\\.|\\s+)JJJ\\b.*\\\="
+ :tests ("member test = 1234"
+ "member this.test = 1234")
+ :not ("member testnot = 1234"
+ "member this.testnot = 1234"))
+
+ (:language "fsharp" :type "type"
+ :supports ("ag" "rg" "grep" "git-grep")
+ :regex "type\\s+JJJ\\b.*\\\="
+ :tests ("type test = 1234")
+ :not ("type testnot = 1234"))
+
+ ;;-- kotlin
+ (:language "kotlin" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "fun\\s*(<[^>]*>)?\\s*JJJ\\s*\\("
+ :tests ("fun test()" "fun <T> test()"))
+
+ (:language "kotlin" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(val|var)\\s*JJJ\\b"
+ :not ("val testval"
+ "var testvar")
+ :tests ("val test "
+ "var test"))
+
+ (:language "kotlin" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(class|interface)\\s*JJJ\\b"
+ :tests ("class test"
+ "class test : SomeInterface"
+ "interface test"))
+
+ ;;-- zig
+ (:language "zig" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "fn\\s+JJJ\\b"
+ :tests ("fn test() void {"
+ "fn test(a: i32) i32 {"
+ "pub fn test(a: i32) i32 {"
+ "export fn test(a: i32) i32 {"
+ "extern \"c\" fn test(a: i32) i32 {"
+ "inline fn test(a: i32) i32 {"))
+
+ (:language "zig" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(var|const)\\s+JJJ\\b"
+ :tests ("const test: i32 = 3;"
+ "var test: i32 = 3;"
+ "pub const test: i32 = 3;"))
+
+ ;;-- protobuf
+ (:language "protobuf" :type "message"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "message\\s+JJJ\\s*\\\{"
+ :tests ("message test{" "message test {"))
+
+ (:language "protobuf" :type "enum"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "enum\\s+JJJ\\s*\\\{"
+ :tests ("enum test{" "enum test {"))
+
+ ;;-- apex (literally the same regexes as java)
+ (:language "apex" :type "function"
+ :supports ("ag" "rg")
+ :regex "^\\s*(?:[\\w\\[\\]]+\\s+){1,3}JJJ\\s*\\\("
+ :tests ("int test()"
+ "int test(param)"
+ "static int test()"
+ "static int test(param)"
+ "public static MyType test()"
+ "private virtual SomeType test(param)"
+ "static int test()"
+ "private foo[] test()")
+ :not ("test()" "testnot()"
+ "blah = new test()"
+ "foo bar = test()"))
+
+ (:language "apex" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*=[^=\\n)]+" :tests ("int test = 1234")
+ :not ("if test == 1234:" "int nottest = 44"))
+
+ (:language "apex" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "(class|interface)\\s*JJJ\\b"
+ :tests ("class test:"
+ "public class test implements Something")
+ :not ("class testnot:"
+ "public class testnot implements Something"))
+
+ ;;-- jai
+ (:language "jai" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*::"
+ :tests ("test ::"))
+ (:language "jai" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*(:|:\\s*=|::)"
+ :tests ("test: Type" "test : Type = Val" "test :: Val"))
+ (:language "jai" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*::"
+ :tests ("test ::"))
+
+ ;;-- odin
+ (:language "odin" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\s*\\bJJJ\\s*:\\s*([^=\\n]+\\s*:|:|[^=\\n]+\\s*=|=)"
+ :tests ("test :: struct"
+ "test ::enum"
+ "test:: union"
+ "test: : custom_type"
+ "test :: [2]f32"
+ "test : f32 : 20"
+ "test: i32 : 10"
+ "test := 20"
+ "test : f32 = 20"
+ "test: i32 = 10"
+ "test: i32= 10"
+ "test :i32= 10"
+ "test :: proc()"
+ "test ::proc() {"
+ "test:: proc(a: i32) -> i32 {"
+ "test::proc{}"
+ "test: :proc \"contextless\" {}"))
+
+ (:language "odin" :type "function"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*:\\s*:\\s*proc\\s*[({]"
+ :tests ("test :: proc(a: int)"
+ "test ::proc()"
+ "test:: proc(a: i32) -> i32 {"
+ "test::proc{}")
+ :not ("testnot :: proc()"
+ "test: proc()"))
+
+ (:language "odin" :type "type"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "\\bJJJ\\s*:\\s*:\\s*(struct|enum|union|bit_set|bit_field)\\b"
+ :tests ("test :: struct {"
+ "test :: enum {"
+ "test :: union {"
+ "test :: bit_set {"
+ "test :: bit_field {")
+ :not ("testnot :: struct {"
+ "test :: structure_builder"))
+
+ ;;-- cobol
+ (:language "cobol" :type "variable"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^(.{6}[^*/])?\\s*([frs]d|[[:digit:]]{1,2})\\s+JJJ([\\. ]|$)"
+ :tests (" 01 test."
+ "000350 10 test pic x."
+ " 88 test value '10'."
+ "fd test."
+ " sd test.")
+ :not (" *01 test"
+ " 05 test-01"
+ " 05 testing"))
+
+ (:language "cobol" :type "section"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^(.{6}[^*/])?JJJ(\\s+section)?\\."
+ :tests (" test."
+ " test section."
+ "test."
+ "test section.")
+ :not (" test."
+ " *test section."))
+
+ (:language "cobol" :type "submodule"
+ :supports ("ag" "grep" "rg" "git-grep")
+ :regex "^(.{6}[^*/])?program-id\\.\\s*[\"']?JJJ[\"']?.*\\."
+ :tests (" program-id. test."
+ " program-id. 'test'."
+ "program-id. \"test\".")
+ :not (" test.")))
+
+ "List of search regex pattern templates organized by language and type.
+Used for generating the grep tool search commands.
+
+For a given language, a regular expression is only used by Dumb Jump when
+the currently used search tool is identified in the :supports value.
+
+The regex string is a template filled by `dumb-jump-populate-regex' and
+should contain the \"JJJ\" string, a marker for the searched identifier.
+
+It may also contain the following:
+
+- \"\\\\j\" : a marker for word-boundary regular-expression to use instead
+ of \"\\\\b\" when word boundary concept must support Lisp language
+ family and must not include the characters '-', '?' and '*'.
+ In the Lisp family of programming languages a large number of
+ characters are allowed in identifiers, including '-', '?' and
+ '*'.
+ This marker is replaced by value of the search tool specific
+ dumb-jump-TOOL-word-boundary, like `dumb-jump-ag-word-boundary' for
+ ag.
+
+- \"\\\\s\" : for a single white space character.
+ Replaced by \"[[:space:]]\" for some search tools as identified by
+ `dumb-jump-use-space-bracket-exp-for' returning t for a tool variant.
+
+The language types supported are listed in the linked page below:
+- Ag:
+ URL \ `https://github.com/ggreer/the_silver_searcher/blob/master/tests/list_file_types.t'
+- rg:
+ URL \ `https://github.com/BurntSushi/ripgrep/blob/master/crates/ignore/src/default_types.rs#L12'
+
+More information using the search tool command line help."
+ :group 'dumb-jump
+ :type
+ '(repeat
+ (plist :options ((:language string)
+ (:type string)
+ (:supports string)
+ (:regex string)
+ (:tests (repeat string))
+ (:not (repeat string))))))
+
+(defcustom dumb-jump-language-file-exts
+ ;; dumb-jump language file extension ag type rg type
+ '((:language "elisp" :ext "el" :agtype "elisp" :rgtype "elisp")
+ (:language "elisp" :ext "el.gz" :agtype "elisp" :rgtype "elisp")
+ (:language "commonlisp" :ext "lisp" :agtype "lisp" :rgtype "lisp")
+ (:language "commonlisp" :ext "lsp" :agtype "lisp" :rgtype "lisp")
+ (:language "c++" :ext "c" :agtype "cc" :rgtype "c")
+ (:language "c++" :ext "h" :agtype "cc" :rgtype "c")
+ (:language "c++" :ext "C" :agtype "cpp" :rgtype "cpp")
+ (:language "c++" :ext "H" :agtype "cpp" :rgtype "cpp")
+ (:language "c++" :ext "tpp" :agtype "cpp" :rgtype nil)
+ (:language "c++" :ext "cpp" :agtype "cpp" :rgtype "cpp")
+ (:language "c++" :ext "hpp" :agtype "cpp" :rgtype "cpp")
+ (:language "c++" :ext "cxx" :agtype "cpp" :rgtype "cpp")
+ (:language "c++" :ext "hxx" :agtype "cpp" :rgtype nil)
+ (:language "c++" :ext "cc" :agtype "cpp" :rgtype "cpp")
+ (:language "c++" :ext "hh" :agtype "cpp" :rgtype "cpp")
+ (:language "c++" :ext "c++" :agtype nil :rgtype nil)
+ (:language "c++" :ext "h++" :agtype nil :rgtype nil)
+ (:language "c++" :ext "ino" :agtype "cpp" :rgtype nil)
+ (:language "coq" :ext "v" :agtype nil :rgtype nil)
+ (:language "ocaml" :ext "ml" :agtype "ocaml" :rgtype "ocaml")
+ (:language "ocaml" :ext "mli" :agtype "ocaml" :rgtype "ocaml")
+ (:language "ocaml" :ext "mll" :agtype "ocaml" :rgtype "ocaml")
+ (:language "ocaml" :ext "mly" :agtype "ocaml" :rgtype "ocaml")
+ ;; groovy is nil type because jenkinsfile is not in searcher type lists
+ (:language "groovy" :ext "gradle" :agtype nil :rgtype nil)
+ (:language "groovy" :ext "groovy" :agtype nil :rgtype nil)
+ (:language "groovy" :ext "jenkinsfile" :agtype nil :rgtype nil)
+ (:language "haskell" :ext "hs" :agtype "haskell" :rgtype "haskell")
+ (:language "haskell" :ext "lhs" :agtype "haskell" :rgtype "haskell")
+ (:language "objc" :ext "m" :agtype "objc" :rgtype "objc")
+ (:language "csharp" :ext "cs" :agtype "csharp" :rgtype "csharp")
+ (:language "java" :ext "java" :agtype "java" :rgtype "java")
+ (:language "vala" :ext "vala" :agtype "vala" :rgtype "vala")
+ (:language "vala" :ext "vapi" :agtype "vala" :rgtype "vala")
+ (:language "julia" :ext "jl" :agtype "julia" :rgtype "julia")
+ (:language "clojure" :ext "clj" :agtype "clojure" :rgtype "clojure")
+ (:language "clojure" :ext "cljc" :agtype "clojure" :rgtype "clojure")
+ (:language "clojure" :ext "cljs" :agtype "clojure" :rgtype "clojure")
+ (:language "clojure" :ext "cljx" :agtype "clojure" :rgtype "clojure")
+ (:language "coffeescript" :ext "coffee" :agtype "coffee" :rgtype "coffeescript")
+ (:language "faust" :ext "dsp" :agtype nil :rgtype nil)
+ (:language "faust" :ext "lib" :agtype nil :rgtype nil)
+ (:language "fennel" :ext "fnl" :agtype nil :rgtype nil)
+ (:language "fortran" :ext "F" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "f" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "f77" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "f90" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "f95" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "F77" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "F90" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "F95" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "f03" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "for" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "ftn" :agtype "fortran" :rgtype "fortran")
+ (:language "fortran" :ext "fpp" :agtype "fortran" :rgtype "fortran")
+ (:language "go" :ext "go" :agtype "go" :rgtype "go")
+ (:language "javascript" :ext "js" :agtype "js" :rgtype "js")
+ (:language "javascript" :ext "jsx" :agtype "js" :rgtype "js")
+ (:language "javascript" :ext "vue" :agtype "js" :rgtype "js")
+ (:language "javascript" :ext "html" :agtype "html" :rgtype "html")
+ (:language "javascript" :ext "css" :agtype "css" :rgtype "css")
+ (:language "typescript" :ext "ts" :agtype "ts" :rgtype "ts")
+ (:language "typescript" :ext "tsx" :agtype "ts" :rgtype "ts")
+ (:language "typescript" :ext "vue" :agtype "ts" :rgtype "ts")
+ (:language "dart" :ext "dart" :agtype nil :rgtype "dart")
+ (:language "dlang" :ext "d" :agtype "dlang" :rgtype "d")
+ (:language "dlang" :ext "di" :agtype "dlang" :rgtype "d")
+ (:language "lua" :ext "lua" :agtype "lua" :rgtype "lua")
+ ;; the extension "m" is also used by obj-c so must use matlab-mode
+ ;; since obj-c will win by file extension, but here for searcher types
+ (:language "matlab" :ext "m" :agtype "matlab" :rgtype "matlab")
+ (:language "nim" :ext "nim" :agtype "nim" :rgtype "nim")
+ (:language "nix" :ext "nix" :agtype "nix" :rgtype "nix")
+ (:language "org" :ext "org" :agtype nil :rgtype "org")
+ (:language "perl" :ext "pl" :agtype "perl" :rgtype "perl")
+ (:language "perl" :ext "pm" :agtype "perl" :rgtype "perl")
+ (:language "perl" :ext "pm6" :agtype "perl" :rgtype nil)
+ (:language "perl" :ext "perl" :agtype nil :rgtype "perl")
+ (:language "perl" :ext "plh" :agtype nil :rgtype "perl")
+ (:language "perl" :ext "plx" :agtype nil :rgtype "perl")
+ (:language "perl" :ext "pod" :agtype "perl" :rgtype "pod")
+ (:language "perl" :ext "t" :agtype "perl" :rgtype nil)
+ (:language "php" :ext "php" :agtype "php" :rgtype "php")
+ (:language "php" :ext "php3" :agtype "php" :rgtype "php")
+ (:language "php" :ext "php4" :agtype "php" :rgtype "php")
+ (:language "php" :ext "php5" :agtype "php" :rgtype "php")
+ (:language "php" :ext "phtml" :agtype "php" :rgtype "php")
+ (:language "php" :ext "inc" :agtype "php" :rgtype nil)
+ (:language "purescript" :ext "purs" :agtype nil :rgtype nil)
+ (:language "python" :ext "py" :agtype "python" :rgtype "py")
+ (:language "r" :ext "R" :agtype "r" :rgtype "r")
+ (:language "r" :ext "r" :agtype "r" :rgtype "r")
+ (:language "r" :ext "Rmd" :agtype "r" :rgtype "r")
+ (:language "r" :ext "Rnw" :agtype "r" :rgtype "r")
+ (:language "r" :ext "Rtex" :agtype "r" :rgtype nil)
+ (:language "r" :ext "Rrst" :agtype "r" :rgtype nil)
+ (:language "racket" :ext "rkt" :agtype "racket" :rgtype "lisp")
+ (:language "crystal" :ext "cr" :agtype "crystal" :rgtype "crystal")
+ (:language "crystal" :ext "ecr" :agtype "crystal" :rgtype nil)
+ (:language "ruby" :ext "rb" :agtype "ruby" :rgtype "ruby")
+ (:language "ruby" :ext "erb" :agtype "ruby" :rgtype nil)
+ (:language "ruby" :ext "haml" :agtype "ruby" :rgtype nil)
+ (:language "ruby" :ext "rake" :agtype "ruby" :rgtype nil)
+ (:language "ruby" :ext "slim" :agtype "ruby" :rgtype nil)
+ (:language "rust" :ext "rs" :agtype "rust" :rgtype "rust")
+ (:language "zig" :ext "zig" :agtype nil :rgtype "zig")
+ (:language "scad" :ext "scad" :agtype nil :rgtype nil)
+ (:language "scala" :ext "scala" :agtype "scala" :rgtype "scala")
+ (:language "scheme" :ext "scm" :agtype "scheme" :rgtype "lisp")
+ (:language "scheme" :ext "ss" :agtype "scheme" :rgtype "lisp")
+ (:language "scheme" :ext "sld" :agtype "scheme" :rgtype "lisp")
+ (:language "janet" :ext "janet" :agtype "janet" :rgtype "lisp")
+ (:language "shell" :ext "sh" :agtype nil :rgtype nil)
+ (:language "shell" :ext "bash" :agtype nil :rgtype nil)
+ (:language "shell" :ext "csh" :agtype nil :rgtype nil)
+ (:language "shell" :ext "ksh" :agtype nil :rgtype nil)
+ (:language "shell" :ext "tcsh" :agtype nil :rgtype nil)
+ (:language "sml" :ext "sml" :agtype "sml" :rgtype "sml")
+ (:language "solidity" :ext "sol" :agtype nil :rgtype nil)
+ (:language "sql" :ext "sql" :agtype "sql" :rgtype "sql")
+ (:language "swift" :ext "swift" :agtype nil :rgtype "swift")
+ (:language "tex" :ext "tex" :agtype "tex" :rgtype "tex")
+ (:language "elixir" :ext "ex" :agtype "elixir" :rgtype "elixir")
+ (:language "elixir" :ext "exs" :agtype "elixir" :rgtype "elixir")
+ (:language "elixir" :ext "eex" :agtype "elixir" :rgtype "elixir")
+ (:language "erlang" :ext "erl" :agtype "erlang" :rgtype "erlang")
+ (:language "systemverilog" :ext "sv" :agtype "verilog" :rgtype "verilog")
+ (:language "systemverilog" :ext "svh" :agtype "verilog" :rgtype "verilog")
+ (:language "vhdl" :ext "vhd" :agtype "vhdl" :rgtype "vhdl")
+ (:language "vhdl" :ext "vhdl" :agtype "vhdl" :rgtype "vhdl")
+ (:language "scss" :ext "scss" :agtype "css" :rgtype "css")
+ (:language "pascal" :ext "pas" :agtype "delphi" :rgtype nil)
+ (:language "pascal" :ext "dpr" :agtype "delphi" :rgtype nil)
+ (:language "pascal" :ext "int" :agtype "delphi" :rgtype nil)
+ (:language "pascal" :ext "dfm" :agtype "delphi" :rgtype nil)
+ (:language "fsharp" :ext "fs" :agtype "fsharp" :rgtype nil)
+ (:language "fsharp" :ext "fsi" :agtype "fsharp" :rgtype nil)
+ (:language "fsharp" :ext "fsx" :agtype "fsharp" :rgtype nil)
+ (:language "kotlin" :ext "kt" :agtype "kotlin" :rgtype "kotlin")
+ (:language "kotlin" :ext "kts" :agtype "kotlin" :rgtype "kotlin")
+ (:language "protobuf" :ext "proto" :agtype "proto" :rgtype "protobuf")
+ (:language "hcl" :ext "tf" :agtype "terraform" :rgtype "tf")
+ (:language "hcl" :ext "tfvars" :agtype "terraform" :rgtype nil)
+ (:language "apex" :ext "cls" :agtype nil :rgtype nil)
+ (:language "apex" :ext "trigger" :agtype nil :rgtype nil)
+ (:language "jai" :ext "jai" :agtype nil :rgtype nil)
+ (:language "odin" :ext "odin" :agtype nil :rgtype nil)
+ (:language "cobol" :ext "cbl" :agtype nil :rgtype nil)
+ (:language "cobol" :ext "cob" :agtype nil :rgtype nil)
+ (:language "cobol" :ext "cpy" :agtype nil :rgtype nil))
+
+ "Map programming language to file extensions support by search tool.
+The value of :agytpe, :rgtype is nil when the search tool file type does
+not search for that file extension, otherwise it is the name of the file
+type for that tool."
+ :group 'dumb-jump
+ :type
+ '(repeat
+ (plist
+ :options ((:language (string :tag "Language"))
+ (:ext (string :tag "Extension"))
+ (:agtype (string :tag "Ag type"))
+ (:rgtype (string :tag "Ripgrep type"))))))
+
+(defcustom dumb-jump-language-contexts
+ '((:language "javascript" :type "function" :left nil :right "^(" )
+ (:language "javascript" :type "variable" :left "($" :right nil )
+ (:language "javascript" :type "variable" :left "($" :right "^)" )
+ (:language "javascript" :type "variable" :left nil :right "^\\." )
+ (:language "javascript" :type "variable" :left nil :right "^;" )
+ (:language "typescript" :type "function" :left nil :right "^(" )
+ (:language "typescript" :type "type" :left "\\(?:[:<>,|&]\\|\\_<\\(?:as\\|extends\\|implements\\)\\_>\\)\\s-*$" :right nil)
+ (:language "typescript" :type "type" :left "\\_<\\(?:type\\|interface\\|enum\\|class\\)\\_>\\s-+$" :right nil)
+ (:language "perl" :type "function" :left nil :right "^(" )
+ (:language "tcl" :type "function" :left "\\[$" :right nil)
+ (:language "tcl" :type "function" :left "^\\s*$" :right nil)
+ (:language "tcl" :type "variable" :left "\\$$" :right nil)
+ (:language "php" :type "function" :left nil :right "^(" )
+ (:language "php" :type "class" :left "new\\s+" :right nil )
+ (:language "elisp" :type "function" :left "($" :right nil )
+ (:language "elisp" :type "variable" :left nil :right "^)" )
+ (:language "scheme" :type "function" :left "($" :right nil )
+ (:language "scheme" :type "variable" :left nil :right "^)" )
+ (:language "jai" :type "function" :left nil :right "\\s*(" )
+ (:language "jai" :type "type" :left "\\s*:\\s*" :right nil)
+ (:language "cobol" :type "submodule" :left "call\\s+" :right nil )
+ (:language "cobol" :type "section" :left "perform\\s+" :right nil )
+ (:language "cobol" :type "section" :left "go to\\s+" :right nil ))
+ "List of under points contexts for each language.
+This helps limit the number of regular expressions we use
+if we know that if there's a ( immediately to the right of
+a symbol then it's probably a function call."
+ :group 'dumb-jump
+ :type
+ '(repeat
+ (plist
+ :options ((:language (string :tag "Language"))
+ (:type (choice (const "function")
+ (const "variable")))
+ (:left (choice (const :tag "Anything" nil)
+ (string :tag "Regular expression")))
+ (:right (choice (const :tag "Anything" nil)
+ (string :tag "Regular expression")))))))
+
+(defcustom dumb-jump-project-denoters
+ '(".dumbjump"
+ ".projectile"
+ ".git"
+ ".hg"
+ ".fslckout"
+ ".bzr"
+ "_darcs"
+ ".svn"
+ "Makefile"
+ "PkgInfo"
+ "-pkg.el"
+ "_FOSSIL_"
+ "Cargo.toml")
+ "Files and directories that signify a directory is a project root.
+If any of these denoting files are present in project sub-directories
+(like Makefile) then you can either remove the culprit denoter from the
+list or create a .dumbjumpignore file in the project sub-directory."
+ :group 'dumb-jump
+ :type '(repeat (string :tag "Name")))
+
+(defcustom dumb-jump-default-project "~"
+ "Directory of default project to search within if a project root is not found."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-project nil
+ "Directory of project to search within if normal denoters will not work.
+This should only be needed in the rarest of cases."
+ :group 'dumb-jump
+ :type 'string)
+
+(defcustom dumb-jump-rust-search-dependencies nil
+ "When non-nil, automatically include Rust dependency source paths.
+Uses `cargo metadata' to discover crate source directories.
+Requires `cargo' to be installed and a Cargo.toml in the project."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(with-no-warnings ; don't warn about long example line
+ (defcustom dumb-jump-extra-search-paths-function nil
+ "Function to compute extra search paths for jump-to-definition lookups.
+When non-nil, this function is called with two arguments: LANG (a
+string naming the programming language, or nil if undetected) and
+PROJ-ROOT (the project root directory). It should return a list of
+directory path strings to include in the search, or nil.
+
+Returned paths may be absolute or relative to PROJ-ROOT (relative
+entries are expanded via `expand-file-name'). Non-existent
+directories and non-string entries are silently ignored.
+
+This is useful for dynamically adding paths such as virtualenv
+site-packages, installed library sources, or SDK directories.
+
+Example:
+
+ (setq dumb-jump-extra-search-paths-function
+ (lambda (lang proj-root)
+ (when (string= lang \"python\")
+ (let* ((default-directory proj-root)
+ (path (string-trim
+ (shell-command-to-string
+ \"poetry run python -c \\\"import site; print(site.getsitepackages()[0])\\\"\"))))
+ (when (file-directory-p path)
+ (list path))))))"
+ :group 'dumb-jump
+ :type '(choice (const :tag "None" nil)
+ function)))
+
+(defcustom dumb-jump-before-jump-hook nil
+ "Hooks called before jumping."
+ :group 'dumb-jump
+ :type 'hook)
+
+(defcustom dumb-jump-after-jump-hook nil
+ "Hooks called after jumping."
+ :group 'dumb-jump
+ :type 'hook)
+
+(defcustom dumb-jump-aggressive
+ nil
+ "If `t` jump aggressively with the possibility of a false positive.
+If `nil` always show list of more than 1 match."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-debug
+ nil
+ "If `t` will print helpful debug information.
+Also ensure that `dumb-jump-quiet' is nil for debugging!"
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-confirm-jump-to-modified-file
+ t
+ "Confirm before jumping to a modified file?
+If t, confirm before jumping to a modified file (which may lead to an
+inaccurate jump).
+If nil, jump without confirmation but print a warning."
+ :group 'dumb-jump
+ :type 'boolean)
+
+(defcustom dumb-jump-disable-obsolete-warnings nil
+ "If non-nil, don't warn about using the legacy interface."
+ :group 'dumb-jump
+ :type 'boolean)
+
+;; ---------------------------------------------------------------------------
+;; Debugging Utilities
+;; -------------------
+
+(defun dumb-jump-message-prin1 (format-str &rest args)
+ "Helper debugging function: generate a FORMAT-STR message with all ARGS."
+ ;; Fist transform each of ARGS to string with `prin1-to-string' then
+ ;; pass them as arguments to message by making a list used the third
+ ;; parameter to apply.
+ (apply 'message format-str (mapcar #'prin1-to-string args)))
+
+;; ---------------------------------------------------------------------------
+;; Search tool presence checkers
+;; -----------------------------
+(defvar dumb-jump--detected-env-problems nil
+ "List of detected environment problems.
+Last detected problem is first in the list.")
+
+(defun dumb-jump-env-problem (problem)
+ "Add PROBLEM string to `dumb-jump--detected-env-problems' list."
+ (unless (member problem dumb-jump--detected-env-problems)
+ (push problem dumb-jump--detected-env-problems)))
+
+;;;###autoload
+(defun dumb-jump-show-detected-problems (keep)
+ "Print all problems detected by dumb-jump environment in *Message*.
+Erase the accumulated list unless the command is issued with any prefix
+key (such as `\\[universal-argument]')."
+ (interactive "P")
+ (if dumb-jump--detected-env-problems
+ (progn
+ (message "-- Dumb-Jump Detected Environment Problems:")
+ (dolist (problem (reverse dumb-jump--detected-env-problems))
+ (message problem))
+ (unless keep
+ (setq dumb-jump--detected-env-problems nil)))
+ (message "No problem recorded yet.")))
+
+;; --
+
+(defvar dumb-jump--ag-installed? 'unset)
+(defun dumb-jump-ag-installed? ()
+ "Return t if ag is installed."
+ (if (eq dumb-jump--ag-installed? 'unset)
+ (setq dumb-jump--ag-installed?
+ (dumb-jump--contains-p "ag version"
+ (shell-command-to-string
+ (concat dumb-jump-ag-cmd " --version"))))
+ dumb-jump--ag-installed?))
+
+(defvar dumb-jump--git-grep-plus-ag-installed? 'unset)
+(defun dumb-jump-git-grep-plus-ag-installed? ()
+ "Return t if git grep and ag is installed."
+ (if (eq dumb-jump--git-grep-plus-ag-installed? 'unset)
+ (setq dumb-jump--git-grep-plus-ag-installed?
+ (and (dumb-jump-git-grep-installed?)
+ (dumb-jump-ag-installed?)))
+ dumb-jump--git-grep-plus-ag-installed?))
+
+(defvar dumb-jump--rg-installed? 'unset)
+(defun dumb-jump-rg-installed? ()
+ "Return t if rg 0.10 or later with PCRE2 support is installed.
+Return nil otherwise. In that case store diagnostics information in the
+variable `dumb-jump--detected-env-problems'."
+ (if (eq dumb-jump--rg-installed? 'unset)
+ (setq
+ dumb-jump--rg-installed?
+ (catch 'rg-problem
+ (unless (executable-find dumb-jump-rg-cmd)
+ (dumb-jump-env-problem
+ (format "Ripgrep not found as specified in `dumb-jump-rg-cmd': %s"
+ dumb-jump-rg-cmd))
+ (throw 'rg-problem nil))
+ (let* ((stdout (shell-command-to-string
+ (concat dumb-jump-rg-cmd " --version")))
+ (has-version (string-match "ripgrep \\([0-9]+\\.[0-9]+\\).*"
+ stdout)))
+ (unless has-version
+ (dumb-jump-env-problem "Can't detect Ripgrep version.")
+ (throw 'rg-problem nil))
+ (unless (version<= "0.10" (match-string-no-properties 1 stdout))
+ (dumb-jump-env-problem "Ripgrep >= 0.10 is not available.")
+ (throw 'rg-problem nil))
+ (unless (string-match "features:.*pcre2" stdout)
+ (dumb-jump-env-problem "Ripgrep does not support PCRE2.")
+ (throw 'rg-problem nil))
+ t)))
+ dumb-jump--rg-installed?))
+
+(defvar dumb-jump--git-grep-installed? 'unset)
+(defun dumb-jump-git-grep-installed? ()
+ "Return t if git-grep is installed."
+ (if (eq dumb-jump--git-grep-installed? 'unset)
+ (setq dumb-jump--git-grep-installed?
+ (dumb-jump--contains-p "fatal: no pattern given"
+ (shell-command-to-string
+ (concat dumb-jump-git-grep-cmd))))
+ dumb-jump--git-grep-installed?))
+
+(defvar dumb-jump--grep-installed? 'unset)
+(defun dumb-jump-grep-installed? ()
+ "Return non-nil if a grep is installed, nil otherwise.
+Return \\='gnu if GNU grep is installed, \\='bsd if BSD grep is installed."
+ (if (eq dumb-jump--grep-installed? 'unset)
+ (let* ((version (shell-command-to-string
+ (concat dumb-jump-grep-cmd " --version")))
+ (variant (cond ((dumb-jump--match "GNU grep" version) 'gnu)
+ ((dumb-jump--match "[0-9]+\\.[0-9]+" version) 'bsd)
+ (t nil))))
+ (setq dumb-jump--grep-installed? variant))
+ dumb-jump--grep-installed?))
+
+;; ---------------------------------------------------------------------------
+;; Dumb-Jump Tests
+;; ---------------
+
+(defun dumb-jump-run-test (test cmd)
+ "Use TEST as the standard input for the CMD."
+ (with-temp-buffer
+ (insert test)
+ (shell-command-on-region (point-min) (point-max) cmd nil t)
+ (buffer-substring-no-properties (point-min) (point-max))))
+
+(defun dumb-jump-run-test-temp-file (test thefile realcmd)
+ "Write TEST string to temporary file THEFILE, run shell REALCMD on it.
+Return resulting string."
+ (with-temp-buffer
+ (insert test)
+ (write-file thefile nil)
+ (delete-region (point-min) (point-max))
+ (shell-command realcmd t)
+ (set-buffer-modified-p nil) ; prevent prompt on file deletion during test
+ (delete-file thefile)
+ (buffer-substring-no-properties (point-min) (point-max))))
+
+(defun dumb-jump-run-git-grep-test (test git-grep-cmd)
+ "Execute GIT-GREP-CMD on input TEST string written in temporary file.
+Necessary because git grep must be given a file as input, not just a string."
+ (let ((thefile ".git.grep.test"))
+ (dumb-jump-run-test-temp-file test
+ thefile
+ (concat git-grep-cmd " " thefile))))
+
+(defun dumb-jump-run-ag-test (test ag-cmd)
+ "Execute AG-CMD on input TEST written in temporary file.
+The difference is that ag ignores multiline matches when passed
+input from stdin, which is a crucial feature."
+ (let ((thefile ".ag.test"))
+ (dumb-jump-run-test-temp-file test
+ thefile
+ (concat ag-cmd " " thefile))))
+
+;; ---------------------------------------------------------------------------
+;; Test dumb-jump search rules
+;; ---------------------------
+
+(defun dumb-jump--search-failure-msg (tool test run-not-tests resp cmd rule)
+ "Return search failure string adjusted to the arguments.
+TOOL: search tool name.
+TEST: the test string.
+RUN-NO-TESTS: t to see a list of all failed rules.
+RESP: response.
+CMD: the search command used.
+RULE: the complete `dumb-jump-find-rules' rule used for this test."
+ (format "%s FAILURE '%s' %s in response '%s' | CMD: '%s' | rule: '%s'"
+ tool
+ test
+ (if run-not-tests "IS unexpectedly" "NOT")
+ resp
+ cmd
+ rule))
+
+(defun dumb-jump-test-grep-rules (&optional run-not-tests)
+ "Test all the grep rules and return count of those that fail.
+Optionally pass t for RUN-NOT-TESTS to see a list of all failed rules."
+ (let ((variant (if (eq (dumb-jump-grep-installed?) 'gnu) 'gnu-grep 'grep)))
+ (mapcan
+ (lambda (rule)
+ (mapcan
+ (lambda (test)
+ (let* ((cmd (concat "grep -En -e "
+ (shell-quote-argument
+ (dumb-jump-populate-regex
+ (plist-get rule :regex) "test" variant))))
+ (resp (dumb-jump-run-test test cmd)))
+ (when (or
+ (and (not run-not-tests) (not (dumb-jump--contains-p test resp)))
+ (and run-not-tests (> (length resp) 0)))
+ (list (dumb-jump--search-failure-msg "grep"
+ test
+ run-not-tests
+ resp
+ cmd
+ (plist-get rule :regex))))))
+ (plist-get rule (if run-not-tests :not :tests))))
+ (seq-filter (lambda (it) (member "grep" (plist-get it :supports))) dumb-jump-find-rules))))
+
+(defun dumb-jump-test-ag-rules (&optional run-not-tests)
+ "Test all the ag rules and return count of those that fail.
+Optionally pass t for RUN-NOT-TESTS to see a list of all failed rules."
+ (mapcan
+ (lambda (rule)
+ (mapcan
+ (lambda (test)
+ (let* ((cmd (concat "ag --nocolor --nogroup --nonumber "
+ (shell-quote-argument
+ (dumb-jump-populate-regex
+ (plist-get rule :regex) "test" 'ag))))
+ (resp (dumb-jump-run-ag-test test cmd)))
+ (when (or
+ (and (not run-not-tests) (not (dumb-jump--contains-p test resp)))
+ (and run-not-tests (> (length resp) 0)))
+ (list (dumb-jump--search-failure-msg "ag"
+ test
+ run-not-tests
+ resp
+ cmd
+ rule)))))
+ (plist-get rule (if run-not-tests :not :tests))))
+ (seq-filter (lambda (it) (member "ag" (plist-get it :supports))) dumb-jump-find-rules)))
+
+(defun dumb-jump-test-rg-rules (&optional run-not-tests)
+ "Test all the rg rules and return count of those that fail.
+Optionally pass t for RUN-NOT-TESTS to see a list of all failed rules."
+ (mapcan
+ (lambda (rule)
+ (mapcan
+ (lambda (test)
+ (let* ((cmd (concat "rg --color never --no-heading -U --pcre2 "
+ (shell-quote-argument
+ (dumb-jump-populate-regex
+ (plist-get rule :regex) "test" 'rg))))
+ (resp (dumb-jump-run-test test cmd)))
+ (when (or
+ (and (not run-not-tests) (not (dumb-jump--contains-p test resp)))
+ (and run-not-tests (> (length resp) 0)))
+ (list (dumb-jump--search-failure-msg "rg"
+ test
+ run-not-tests
+ resp
+ cmd
+ rule)))))
+ (plist-get rule (if run-not-tests :not :tests))))
+ (seq-filter (lambda (it) (member "rg" (plist-get it :supports))) dumb-jump-find-rules)))
+
+(defun dumb-jump-test-git-grep-rules (&optional run-not-tests)
+ "Test all the git grep rules and return count of those that fail.
+Optionally pass t for RUN-NOT-TESTS to see a list of all failed rules.
+
+Note: Tests rules that support \"grep\" since git-grep uses the same
+ERE syntax. Testing actual git-grep would require temp git repos."
+ (mapcan
+ (lambda (rule)
+ (mapcan
+ (lambda (test)
+ (let* ((cmd (concat "git grep --color=never -h --untracked -E "
+ (shell-quote-argument
+ (dumb-jump-populate-regex
+ (plist-get rule :regex) "test" 'git-grep))))
+ (resp (dumb-jump-run-git-grep-test test cmd)))
+ (when (or
+ (and (not run-not-tests) (not (dumb-jump--contains-p test resp)))
+ (and run-not-tests (> (length resp) 0)))
+ (list (dumb-jump--search-failure-msg "git-grep"
+ test
+ run-not-tests
+ resp
+ cmd
+ rule)))))
+ (plist-get rule (if run-not-tests :not :tests))))
+ (seq-filter (lambda (it) (member "grep" (plist-get it :supports))) dumb-jump-find-rules)))
+
+;; ---------------------------------------------------------------------------
+
+(defun dumb-jump-message (str &rest args)
+ "Log message STR with ARGS to *Messages* if not using `dumb-jump-quiet'."
+ (unless dumb-jump-quiet
+ (apply 'message str args))
+ nil)
+
+(defmacro dumb-jump-debug-message (&rest exprs)
+ "Generate a debug message to print all expressions EXPRS."
+ (declare (indent defun))
+ (let ((i 5) frames frame)
+ ;; based on https://emacs.stackexchange.com/a/2312
+ (while (setq frame (backtrace-frame i))
+ (push frame frames)
+ (cl-incf i))
+ ;; this is a macro-expanded version of the code in the stackexchange
+ ;; code from above. This version should work on emacs-24.3, since it
+ ;; doesn't depend on thread-last.
+ (let* ((frame (cl-find-if
+ (lambda (frame)
+ (ignore-errors
+ (and (car frame)
+ (eq (caaddr frame)
+ 'defalias))))
+ (reverse frames)))
+ (func (cl-cadadr (cl-caddr frame)))
+ (defun-name (symbol-name func)))
+ (with-temp-buffer
+ (insert "DUMB JUMP DEBUG `")
+ (insert defun-name)
+ (insert "` START\n----\n\n")
+ (dolist (expr exprs)
+ (insert (prin1-to-string expr) ":\n\t%s\n\n"))
+ (insert "\n-----\nDUMB JUMP DEBUG `")
+ (insert defun-name)
+ (insert "` END\n-----")
+ `(when dumb-jump-debug
+ (dumb-jump-message
+ ,(buffer-string)
+ ,@exprs))))))
+
+(defun dumb-jump-get-point-context (line func cur-pos)
+ "Get the LINE context to the left and right of FUNC using CUR-POS as hint."
+ (let ((loc (or (cl-search func line :start2 cur-pos) 0)))
+ (list :left (substring line 0 loc)
+ :right (substring line (+ loc (length func))))))
+
+(defun dumb-jump-to-selected (results choices selected)
+ "With RESULTS use CHOICES to find the SELECTED choice from multiple options."
+ (let* ((result-index (dumb-jump--find-index (lambda (it) (string= selected it)) choices))
+ (result (when result-index
+ (nth result-index results))))
+ (when result
+ (dumb-jump-result-follow result))))
+
+(defun dumb-jump-helm-persist-action (candidate)
+ "Preview CANDIDATE in temporary buffer displaying the file at matched line.
+\\<helm-map>
+This is the persistent action (\\[helm-execute-persistent-action]) for helm."
+ (let* ((file (plist-get candidate :path))
+ (line (plist-get candidate :line))
+ (default-directory-old default-directory))
+ (switch-to-buffer (get-buffer-create " *helm dumb jump persistent*"))
+ (setq default-directory default-directory-old)
+ (fundamental-mode)
+ (erase-buffer)
+ (insert-file-contents file)
+ (let ((buffer-file-name file))
+ (set-auto-mode)
+ (font-lock-fontify-region (point-min) (point-max))
+ (goto-char (point-min))
+ (forward-line (1- line)))))
+
+(defun dumb-jump--format-result (proj-root result)
+ "Return file:line:target formatted RESULT as a string stripped of PROJ-ROOT."
+ (format "%s:%s: %s"
+ (dumb-jump--replace proj-root "" (plist-get result :path))
+ (plist-get result :line)
+ (dumb-jump--trim (plist-get result :context))))
+
+(defun dumb-jump-ivy-jump-to-selected (results choices _proj)
+ "Offer CHOICES as candidates through `ivy-read'.
+Then execute dumb-jump-result-follow' on the selected choice RESULTS.
+Ignore _PROJ."
+ (if (fboundp 'ivy-read)
+ (ivy-read "Jump to: " (dumb-jump--zip-pair choices results)
+ :action (lambda (cand)
+ (dumb-jump-result-follow (cdr cand)))
+ :caller 'dumb-jump-ivy-jump-to-selected)
+ (error "ivy-read is unknown. Is it loaded?")))
+
+(defun dumb-jump-prompt-user-for-choice (proj-root results)
+ "Prompt user to select one of the identified RESULTS in PROJ-ROOT.
+Strip the PROJ-ROOT (project root path) from the file name shown in each entry.
+Prompt with the mechanism selected by `dumb-jump-selector' user-option if
+available otherwise default to `completing-read'."
+ (let ((choices (mapcar (lambda (it) (dumb-jump--format-result proj-root it)) results)))
+ (cond
+ ((eq dumb-jump-selector 'completing-read)
+ (dumb-jump-to-selected results choices
+ (dumb-jump--select-choice choices "Jump to: ")))
+ ;;
+ ((and (eq dumb-jump-selector 'ivy)
+ (fboundp 'ivy-read))
+ (funcall dumb-jump-ivy-jump-to-selected-function
+ results choices proj-root))
+ ;;
+ ((and (eq dumb-jump-selector 'helm)
+ (fboundp 'helm))
+ (helm :sources
+ (with-no-warnings
+ (helm-make-source "Jump to: " 'helm-source-sync
+ :action '(("Jump to match" . dumb-jump-result-follow))
+ :candidates (dumb-jump--zip-pair choices results)
+ :persistent-action 'dumb-jump-helm-persist-action))
+ :buffer "*helm dumb jump choices*"))
+ ;; fallback for unsupported selector values
+ (t (dumb-jump-to-selected results choices
+ (dumb-jump--select-choice choices "Jump to: "))))))
+
+(defun dumb-jump-get-project-root (filepath)
+ "Return project root holding FILEPATH.
+Keep looking at the parent dir of FILEPATH until a denoter file/dir is found.
+Fall back to `project.el' when available and no denoter is found.
+Return a directory name without the trailing slash."
+ (let* ((path (expand-file-name filepath))
+ (dir (if (file-directory-p path)
+ (file-name-as-directory path)
+ (file-name-directory path)))
+ (project-el-root
+ (when (and (null dumb-jump-project)
+ dir
+ (fboundp 'project-current))
+ (let ((default-directory dir))
+ (let ((proj (ignore-errors (project-current nil))))
+ (when proj
+ (if (fboundp 'project-root)
+ (project-root proj)
+ (with-no-warnings
+ ;; On Emacs 26.1 use now obsolete `project-roots'
+ (car (project-roots proj))))))))))
+ (directory-file-name
+ (expand-file-name
+ (or dumb-jump-project
+ (locate-dominating-file path #'dumb-jump-get-config)
+ project-el-root
+ dumb-jump-default-project)))))
+
+(defvar dumb-jump--rust-deps-cache (make-hash-table :test 'equal)
+ "Cache of Rust dependency paths keyed by project root.")
+
+(defun dumb-jump-get-rust-dependency-paths (proj-root)
+ "Return list of source directories for Rust dependencies in PROJ-ROOT.
+Uses `cargo metadata' to discover dependency crate source paths.
+Results are cached per project root."
+ (let ((cached (gethash proj-root dumb-jump--rust-deps-cache 'dumb-jump--no-cache)))
+ (if (not (eq cached 'dumb-jump--no-cache))
+ cached
+ (let ((dep-paths
+ (when (file-exists-p (expand-file-name "Cargo.toml" proj-root))
+ (let* ((default-directory proj-root)
+ (json-output (with-temp-buffer
+ (let ((exit-code (call-process "cargo" nil t nil
+ "metadata" "--format-version=1")))
+ (when (eq exit-code 0)
+ (buffer-string)))))
+ (json-data (ignore-errors (when json-output (json-read-from-string json-output))))
+ (packages (when json-data (cdr (assoc 'packages json-data))))
+ (paths (when packages
+ (seq-uniq
+ (seq-filter
+ #'file-directory-p
+ (mapcar (lambda (pkg)
+ (file-name-directory
+ (cdr (assoc 'manifest_path pkg))))
+ packages))))))
+ (seq-remove
+ (lambda (p) (string= (directory-file-name p)
+ (directory-file-name proj-root)))
+ paths)))))
+ (puthash proj-root dep-paths dumb-jump--rust-deps-cache)
+ dep-paths))))
+
+(defun dumb-jump-get-config (dir)
+ "If a project denoter is in DIR then return it, otherwise nil.
+However, if DIR contains a `.dumbjumpignore' it returns nil to keep
+looking for another root."
+ (if (file-exists-p (expand-file-name ".dumbjumpignore" dir))
+ nil
+ (car (seq-filter
+ (lambda (it) (file-exists-p (expand-file-name it dir)))
+ dumb-jump-project-denoters))))
+
+(defun dumb-jump-get-language (file)
+ "Get language from FILE extension. Fallback to using `major-mode' name."
+ (let* ((language (or (dumb-jump-get-language-from-mode)
+ (dumb-jump-get-language-by-filename file)
+ (dumb-jump-get-mode-base-name))))
+ ; src edit buffer ? org-edit-src-exit
+ (if (and (fboundp 'org-src-edit-buffer-p)
+ (org-src-edit-buffer-p))
+ (progn
+ (setq language "org")
+ (with-no-warnings
+ (org-edit-src-exit))
+ (if (and (boundp 'org-version)
+ (version< org-version "9"))
+ (save-buffer))))
+ (if (string= language "org")
+ (setq language (dumb-jump-get-language-in-org)))
+ (if (member language (seq-uniq
+ (mapcar (lambda (it) (plist-get it :language))
+ dumb-jump-find-rules)))
+ language
+ (format ".%s file" (or (file-name-extension file) "")))))
+
+(defun dumb-jump-get-language-in-org ()
+ "Return associated language in org mode src block or \"org\" if outside src.
+This assumes point is inside an org mode buffer."
+ (let ((lang (nth 0 (with-no-warnings (org-babel-get-src-block-info)))))
+ ;; if lang exists then create a composite language
+ (if lang
+ (dumb-jump-make-composite-language
+ "org"
+ (if (dumb-jump-get-language-from-aliases lang)
+ (dumb-jump-get-language-from-aliases lang)
+ lang)
+ "org"
+ "org"
+ "org")
+ "org")))
+
+(defun dumb-jump-add-language-to-proplist (newlang proplist lang)
+ "Return nil if NEWLANG is already in PROPLIST or (if not)
+return a new proplist. The new proplis is PROPLIS
+where a NEWLANG plist(s) is (are) added to PROPLIST.
+The plist(s) value of NEWLANG is (are) copied from
+those of LANG and LANG is replaced by NEWLANG."
+ (unless (seq-filter (lambda (it) (string= newlang (plist-get it :language)))
+ proplist)
+ (dumb-jump--splice
+ (lambda (it) (string= lang (plist-get it :language)))
+ (lambda (it) (list it (plist-put (copy-tree it) :language newlang)))
+ proplist)))
+
+(defun dumb-jump-make-composite-language (mode lang extension agtype rgtype)
+ "Concat one MODE (usually the string org) with a LANG (c or python or etc)
+to make a composite language of the form cPLUSorg or pythonPLUSorg or etc.
+Modify `dumb-jump-find-rules' and `dumb-jump-language-file-exts' accordingly
+\(using EXTENSION AGTYPE RGTYPE)"
+ (let* ((complang (concat lang "PLUS" mode))
+ (alreadyextension (seq-filter (lambda (it)
+ (and
+ (string= complang (plist-get it :language))
+ (string= extension (plist-get it :ext))
+ (string= agtype (plist-get it :agtype))
+ (string= rgtype (plist-get it :rgtype))))
+ dumb-jump-language-file-exts))
+ (newfindrule (dumb-jump-add-language-to-proplist complang dumb-jump-find-rules lang))
+ (newfileexts (dumb-jump-add-language-to-proplist complang dumb-jump-language-file-exts lang)))
+ (when newfindrule
+ (set-default 'dumb-jump-find-rules newfindrule))
+ (unless dumb-jump-search-type-org-only-org
+ (when newfileexts
+ (set-default 'dumb-jump-language-file-exts newfileexts)))
+ (unless alreadyextension
+ (set-default 'dumb-jump-language-file-exts
+ (cons `(:language ,complang
+ :ext ,extension
+ :agtype ,agtype
+ :rgtype ,rgtype)
+ dumb-jump-language-file-exts)))
+ complang))
+
+(defcustom dumb-jump-language-aliases-alist
+ '(("sh" . "shell")
+ ("d" . "dlang")
+ ("cperl" . "perl")
+ ("octave" . "matlab")
+ ("emacs-lisp" . "elisp")
+ ("R" . "r")
+ ("arduino" . "c++"))
+ "An alist of language alias to language name.
+Aliases are major mode base name sometimes used for some files.
+For example: \"cperl\" for the \"perl\" language."
+ :group 'dumb-jump
+ :type '(repeat (cons
+ (string :tag "alias ")
+ (string :tag "language"))))
+
+(defun dumb-jump-get-language-from-aliases (lang)
+ "Return the dumb-jump language name for a possible alias if there is one.
+Return nil if LANG is not one of the supported aliases."
+ (assoc-default lang dumb-jump-language-aliases-alist))
+
+(defun dumb-jump-get-mode-base-name ()
+ "Get the base name of the mode."
+ (dumb-jump--replace "-mode" "" (symbol-name major-mode)))
+
+(defun dumb-jump-get-language-from-mode ()
+ "Extract the language from the `major-mode' name.
+Currently just everything before \"-mode\"."
+ (dumb-jump-get-language-from-aliases (dumb-jump-get-mode-base-name)))
+
+(defun dumb-jump-get-language-by-filename (file)
+ "Get the programming language name from the specified FILE extension."
+ ;; Search in `dumb-jump-language-file-exts' for an entry that has the :ext
+ ;; attribute equal to the file extension and return the corresponding
+ ;; :language value.
+ (let* ((filename (if (string-suffix-p ".gz" file)
+ (file-name-sans-extension file)
+ file))
+ (result (seq-filter
+ (lambda (it) (string-suffix-p (concat "." (plist-get it :ext)) filename))
+ dumb-jump-language-file-exts)))
+ (when (and result (eq (length result) 1))
+ (plist-get (car result) :language))))
+
+;; --
+
+(defun dumb-jump-issue-result (issue)
+ "Return a result property list with the ISSUE set as :issue property symbol."
+ `(:results nil
+ :lang nil
+ :symbol nil
+ :ctx-type nil
+ :file nil
+ :root nil
+ :issue ,(intern issue)))
+
+(defun dumb-jump-get-results (&optional entered-name)
+ "Perform the dumb-jump search for text at point, region or ENTERED-NAME.
+
+Runs `dumb-jump-fetch-results' under conditions adjusted to the current
+context.
+
+Perform the search if searcher is installed, buffer is saved, and
+there's a symbol under point or an item to search is specified by the
+ENTERED-NAME string.
+
+Return the dumb-jump alist result:
+- :results
+- :lang : string: language name
+- :symbol : string: symbol being searched
+- :ctx-type: string:
+- :file : string: file name
+- :root : string: project root
+- :issue : symbol: problem description symbol: nogrep or nosymbol."
+ ;; Prevent a empty string entered-name from being interpreted as user entry
+ ;; because `read-from-minibuffer' may return "".
+ (when (and entered-name (string-blank-p entered-name))
+ (setq entered-name nil))
+ (cond
+ ;; When no search tool is available just return an empty result identifying
+ ;; a nogrep issue.
+ ((not (or (dumb-jump-ag-installed?)
+ (dumb-jump-rg-installed?)
+ (dumb-jump-git-grep-installed?)
+ (dumb-jump-grep-installed?)))
+ (dumb-jump-issue-result "nogrep"))
+ ;; Inside a shell or eshell buffer,
+ ((derived-mode-p 'shell-mode 'eshell-mode)
+ (dumb-jump-fetch-shell-results entered-name))
+ ;; When inside a normal buffer but no string specified by ENTERED-NAME,
+ ;; point or region does not identify something to search,
+ ;; just return an empty result identifying a nosymbol issue.
+ ((and (not entered-name)
+ (not (region-active-p))
+ (not (thing-at-point 'symbol)))
+ (dumb-jump-issue-result "nosymbol"))
+ ;; Otherwise, perform the search for the text identified by string specified
+ ;; by ENTERED-NAME, point or region.
+ (t
+ (dumb-jump-fetch-file-results entered-name))))
+
+(defun dumb-jump-fetch-shell-results (&optional entered-name)
+ "Perform search from shell buffer for text at point, region or ENTERED-NAME.
+Return the dumb-jump alist result:
+- :results
+- :lang : string: language name
+- :symbol : string: symbol being searched
+- :ctx-type: string:
+- :file : string: file name
+- :root : string: project root."
+ (let* ((cur-file (buffer-name))
+ (proj-root (dumb-jump-get-project-root default-directory))
+ (proj-config (dumb-jump-get-config proj-root))
+ (config (when (string-suffix-p ".dumbjump" proj-config)
+ (dumb-jump-read-config proj-root proj-config)))
+ (lang (or (plist-get config :language)
+ (car (dumb-jump-get-lang-by-shell-contents (buffer-name))))))
+ (dumb-jump-fetch-results cur-file proj-root lang config entered-name)))
+
+(defun dumb-jump-fetch-file-results (&optional entered-name)
+ "Perform a file search for text at point, region or ENTERED-NAME string.
+Return the dumb-jump alist result:
+- :results
+- :lang : string: language name
+- :symbol : string: symbol being searched
+- :ctx-type: string:
+- :file : string: file name
+- :root : string: project root."
+ (let* ((cur-file (or (buffer-file-name) ""))
+ (proj-root (dumb-jump-get-project-root cur-file))
+ (proj-config (dumb-jump-get-config proj-root))
+ (config (when (string-suffix-p ".dumbjump" proj-config)
+ (dumb-jump-read-config proj-root proj-config)))
+ (lang (or (plist-get config :language)
+ (dumb-jump-get-language cur-file))))
+ (dumb-jump-fetch-results cur-file proj-root lang config entered-name)))
+
+;; --
+(defun dumb-jump-process-symbol-by-lang (lang look-for)
+ "Process and potentially transform LANG's LOOK-FOR.
+For instance, remove clojure namespace prefix."
+ (cond
+ ((and (string= lang "commonlisp")
+ (dumb-jump--contains-p ":" look-for)
+ (not (string-prefix-p ":" look-for)))
+ (nth 1 (dumb-jump--split ":" look-for 'omit-nulls)))
+ ((and (string= lang "clojure")
+ (dumb-jump--contains-p "/" look-for))
+ (nth 1 (dumb-jump--split "/" look-for)))
+ ((and (string= lang "fennel")
+ (dumb-jump--contains-p "." look-for))
+ (car (last (dumb-jump--split "\\." look-for))))
+ ((and (string= lang "ruby")
+ (dumb-jump--contains-p "::" look-for))
+ (car (last (dumb-jump--split "::" look-for))))
+ ((and (or (string= lang "ruby")
+ (string= lang "crystal"))
+ (string-prefix-p ":" look-for))
+ (dumb-jump--chop-prefix ":" look-for))
+ ((and (string= lang "systemverilog")
+ (string-prefix-p "`" look-for))
+ (dumb-jump--chop-prefix "`" look-for))
+ (t
+ look-for)))
+
+(defun dumb-jump-get-point-line ()
+ "Get line at point."
+ (thing-at-point 'line t))
+
+(defun dumb-jump-get-point-symbol ()
+ "Get symbol at point."
+ (if (region-active-p)
+ (buffer-substring-no-properties (region-beginning) (region-end))
+ (thing-at-point 'symbol t)))
+
+(defun dumb-jump--get-symbol-start ()
+ "Get the start of symbol at point."
+ (- (if (region-active-p)
+ (region-beginning)
+ (car (bounds-of-thing-at-point 'symbol)))
+ (line-beginning-position)))
+
+(defun dumb-jump-get-lang-by-shell-contents (buffer)
+ "Return languages in BUFFER by checking if file extension is mentioned."
+ (let* ((buffer-contents (with-current-buffer buffer
+ (buffer-string)))
+ (found (seq-filter (lambda (it)
+ (dumb-jump--match (concat "\\." (regexp-quote (plist-get it :ext)) "\\b")
+ buffer-contents))
+ dumb-jump-language-file-exts)))
+ (mapcar (lambda (it) (plist-get it :language)) found)))
+
+(defun dumb-jump-fetch-results (cur-file proj-root lang config
+ &optional entered-name)
+ "Search for symbol in ENTERED-NAME or symbol at point.
+
+Return a list of results based on current file context and calling the
+currently selected searcher tool (grep, ag, rg, ...).
+
+CUR-FILE is the path of the current buffer.
+PROJ-ROOT is that file's root project directory.
+LANG is a string programming language with CONFIG a property list
+of project configuration.
+ENTERED-NAME is an optional string identifying item to search.
+The returned property list has the following members:
+- :results:
+- :lang : string: language name
+- :symbol : string: symbol being searched
+- :ctx-type: string:
+- :file : string: file name
+- :root : string: project root."
+ (let* ((cur-line-num (line-number-at-pos))
+ (found-symbol (or entered-name (dumb-jump-get-point-symbol)))
+ (look-for (dumb-jump-process-symbol-by-lang lang found-symbol))
+ (pt-ctx (if entered-name
+ (get-text-property 0 :dumb-jump-ctx entered-name)
+ (dumb-jump-get-point-context
+ (dumb-jump-get-point-line)
+ look-for
+ (dumb-jump--get-symbol-start))))
+ (ctx-type
+ (unless (eq dumb-jump--search-mode 'references)
+ (dumb-jump-get-ctx-type-by-language lang pt-ctx)))
+
+ (gen-funcs (dumb-jump-pick-grep-variant proj-root))
+ (parse-fn (plist-get gen-funcs :parse)) ; dumb-jump-parse-TOOL-response
+ (generate-fn (plist-get gen-funcs :generate))
+ (searcher (plist-get gen-funcs :searcher))
+
+ (regexes (if (eq dumb-jump--search-mode 'references)
+ ;; Broad symbol search with a left boundary that also
+ ;; works for Lisp-family identifiers like *foo*.
+ (list "(^|[^a-zA-Z0-9\\?\\*-])JJJ\\j")
+ (dumb-jump-get-contextual-regexes lang ctx-type searcher)))
+
+ ;; If selected searcher has no rules for this language, try alternatives.
+ ;; Only block fallback when dumb-jump-force-searcher is a direct
+ ;; searcher symbol. When it's a function or directory list that
+ ;; didn't match this project, the auto-selected searcher should
+ ;; still be eligible for fallback.
+ (_alt-searcher
+ (when (and (null regexes)
+ (not (eq dumb-jump--search-mode 'references))
+ (not (memq dumb-jump-force-searcher
+ '(ag rg grep gnu-grep git-grep
+ git-grep-plus-ag))))
+ (let ((alt (seq-find
+ (lambda (s)
+ (and (not (eq s searcher))
+ (if (eq s 'gnu-grep)
+ (eq (dumb-jump-grep-installed?) 'gnu)
+ (funcall (plist-get
+ (dumb-jump-generators-by-searcher s)
+ :installed)))
+ (dumb-jump-get-rules-by-language lang s)))
+ '(ag rg git-grep gnu-grep grep))))
+ (when alt
+ (setq gen-funcs (dumb-jump-generators-by-searcher alt))
+ (setq parse-fn (plist-get gen-funcs :parse))
+ (setq generate-fn (plist-get gen-funcs :generate))
+ (setq searcher (plist-get gen-funcs :searcher))
+ (setq regexes (dumb-jump-get-contextual-regexes
+ lang ctx-type searcher))))))
+
+ (exclude-paths (when config (plist-get config :exclude)))
+ (include-paths (when config (plist-get config :include)))
+ (rust-dep-paths (when (and (string= lang "rust")
+ dumb-jump-rust-search-dependencies)
+ (dumb-jump-get-rust-dependency-paths proj-root)))
+ (extra-paths
+ (when dumb-jump-extra-search-paths-function
+ (let* ((hook-lang (if (and (stringp lang)
+ (string-match "\\`\\(.+\\)PLUS" lang))
+ (match-string 1 lang)
+ lang))
+ (paths (funcall dumb-jump-extra-search-paths-function
+ hook-lang proj-root)))
+ (when (listp paths)
+ (mapcan
+ (lambda (p)
+ (when (stringp p)
+ (let ((expanded (expand-file-name p proj-root)))
+ (if (file-directory-p expanded)
+ (list expanded)
+ (when dumb-jump-debug
+ (dumb-jump-message
+ "Dumb Jump: extra-search-paths dropping %S (not a directory)"
+ expanded))
+ nil))))
+ paths)))))
+ ;; we will search proj root and all include paths
+ (search-paths (seq-uniq (append (list proj-root)
+ include-paths
+ (or rust-dep-paths '())
+ (or extra-paths '()))))
+ (_search-paths-debug
+ (when dumb-jump-debug
+ (dumb-jump-message "Dumb Jump: search-paths = %S" search-paths)))
+ (raw-results (mapcan
+ (lambda (it)
+ (dumb-jump-run-command look-for it
+ regexes lang
+ exclude-paths cur-file
+ cur-line-num parse-fn
+ generate-fn))
+ search-paths))
+
+ (results
+ (let ((deduped (delete-dups
+ (mapcar (lambda (it) (plist-put it :target look-for))
+ raw-results))))
+ (if (eq dumb-jump--search-mode 'references)
+ (dumb-jump-filter-definition-results deduped lang look-for searcher)
+ deduped))))
+
+ `(:results ,results
+ :lang ,(if (null lang) "" lang)
+ :symbol ,look-for
+ :ctx-type ,(if (null ctx-type) "" ctx-type)
+ :file ,cur-file
+ :root ,proj-root)))
+
+;;;###autoload
+(defun dumb-jump-back ()
+ "Jump back to where the last jump was done."
+ ;; Note: made obsolete by Emacs `xref-pop-marker-stack'.
+ (interactive)
+ (with-demoted-errors "Error running `dumb-jump-before-jump-hook': %S"
+ (run-hooks 'dumb-jump-before-jump-hook))
+ (pop-tag-mark)
+ (with-demoted-errors "Error running `dumb-jump-after-jump-hook': %S"
+ (run-hooks 'dumb-jump-after-jump-hook)))
+
+;;;###autoload
+(defun dumb-jump-quick-look ()
+ "Run `dumb-jump-go' in quick look mode.
+That is, show a tooltip of where it would jump instead."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive)
+ (with-no-warnings
+ (dumb-jump-go t)))
+
+;;;###autoload
+(defun dumb-jump-go-other-window ()
+ "Like dumb-jump-go' but use `find-file-other-window' instead of `find-file'."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive)
+ (let ((dumb-jump-window 'other))
+ (with-no-warnings
+ (dumb-jump-go))))
+
+;;;###autoload
+(defun dumb-jump-go-current-window ()
+ "Like `dumb-jump-go' but always use `find-file'."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive)
+ (let ((dumb-jump-window 'current))
+ (with-no-warnings
+ (dumb-jump-go))))
+
+;;;###autoload
+(defun dumb-jump-go-prefer-external ()
+ "Like `dumb-jump-go' but prefer external matches from the current file."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive)
+ (with-no-warnings
+ (dumb-jump-go nil t)))
+
+;;;###autoload
+(defun dumb-jump-find-references ()
+ "Find references/usages of the symbol at point.
+This is the inverse of `dumb-jump-go' -- it finds where a symbol
+is used rather than where it is defined."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive)
+ (let ((dumb-jump--search-mode 'references)
+ (dumb-jump-aggressive nil))
+ (with-no-warnings
+ (dumb-jump-go))))
+
+;;;###autoload
+(defun dumb-jump-go-prompt ()
+ "Like `dumb-jump-go' but prompt for function instead of using under point."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive)
+ (with-no-warnings
+ (dumb-jump-go nil nil (read-from-minibuffer "Jump to: "))))
+
+;;;###autoload
+(defun dumb-jump-go-prefer-external-other-window ()
+ "Like `dumb-jump-go-prefer-external' but create another window.
+It uses `find-file-other-window' instead of `find-file'."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive)
+ (let ((dumb-jump-window 'other))
+ (with-no-warnings
+ (dumb-jump-go-prefer-external))))
+
+;;;###autoload
+(defun dumb-jump-go (&optional use-tooltip prefer-external entered-name)
+ "Go to the function/variable declaration for thing at point.
+When USE-TOOLTIP is t a tooltip jump preview will show instead.
+When PREFER-EXTERNAL is t it will sort external matches before
+current file.
+The optional ENTERED-NAME string is the name of the item to jump to
+manually selected by user as response to a prompt."
+ ;; Note: independent of the Emacs xref interface.
+ (interactive "P")
+ (let* ((start-time (float-time))
+ (info (dumb-jump-get-results entered-name))
+ (end-time (float-time))
+ (fetch-time (- end-time start-time))
+ (results (plist-get info :results))
+ (look-for (or entered-name (plist-get info :symbol)))
+ (proj-root (plist-get info :root))
+ (issue (plist-get info :issue))
+ (lang (plist-get info :lang))
+ (result-count (length results)))
+ (when (> fetch-time dumb-jump-max-find-time)
+ (dumb-jump-message
+ "Took over %ss to find '%s'. \
+Please install ag or rg, or add a .dumbjump file to '%s' with path exclusions"
+ (number-to-string dumb-jump-max-find-time) look-for proj-root))
+ (cond
+ ((eq issue 'nogrep)
+ (dumb-jump-message "Please install ag, rg, git grep or grep!"))
+ ((eq issue 'nosymbol)
+ (dumb-jump-message "No symbol under point."))
+ ((string-suffix-p " file" lang)
+ (dumb-jump-message "Could not find rules for '%s'." lang))
+ ((= result-count 1)
+ (dumb-jump-result-follow (car results) use-tooltip proj-root))
+ ((> result-count 1)
+ ;; multiple results so let the user pick from a list
+ ;; unless the match is in the current file
+ (dumb-jump-handle-results results
+ (plist-get info :file)
+ proj-root
+ (plist-get info :ctx-type)
+ look-for use-tooltip prefer-external lang))
+ ((= result-count 0)
+ (dumb-jump-message "'%s' %s %s %s not found."
+ look-for
+ (if (string-blank-p (or lang ""))
+ "with unknown language so"
+ lang)
+ (plist-get info :ctx-type)
+ (if (eq dumb-jump--search-mode 'references)
+ "references"
+ "declaration"))))))
+
+(defcustom dumb-jump-language-comments
+ '((:comment "//" :language "c++")
+ (:comment "/*" :language "c++")
+ (:comment ";" :language "elisp")
+ (:comment ";" :language "commonlisp")
+ (:comment "//" :language "javascript")
+ (:comment "//" :language "typescript")
+ (:comment "//" :language "dart")
+ (:comment "//" :language "dlang")
+ (:comment "/*" :language "dlang")
+ (:comment "/+" :language "dlang")
+ (:comment "--" :language "haskell")
+ (:comment "--" :language "lua")
+ (:comment "//" :language "rust")
+ (:comment "#" :language "julia")
+ (:comment "//" :language "objc")
+ (:comment "//" :language "csharp")
+ (:comment "//" :language "java")
+ (:comment ";" :language "clojure")
+ (:comment "#" :language "coffeescript")
+ (:comment "//" :language "faust")
+ (:comment ";" :language "fennel")
+ (:comment "!" :language "fortran")
+ (:comment "//" :language "go")
+ (:comment "//" :language "zig")
+ (:comment "#" :language "perl")
+ (:comment "#" :language "tcl")
+ (:comment "//" :language "php")
+ (:comment "#" :language "php")
+ (:comment "--" :language "purescript")
+ (:comment "#" :language "python")
+ (:comment "%" :language "matlab")
+ (:comment "#" :language "r")
+ (:comment ";" :language "racket")
+ (:comment "#" :language "ruby")
+ (:comment "#" :language "crystal")
+ (:comment "#" :language "nim")
+ (:comment "#" :language "nix")
+ (:comment "//" :language "scala")
+ (:comment ";" :language "scheme")
+ (:comment "#" :language "janet")
+ (:comment "#" :language "shell")
+ (:comment "//" :language "solidity")
+ (:comment "//" :language "swift")
+ (:comment "#" :language "elixir")
+ (:comment "%" :language "erlang")
+ (:comment "%" :language "tex")
+ (:comment "//" :language "systemverilog")
+ (:comment "--" :language "vhdl")
+ (:comment "//" :language "scss")
+ (:comment "//" :language "pascal")
+ (:comment "//" :language "protobuf")
+ (:comment "#" :language "hcl")
+ (:comment "//" :language "apex")
+ (:comment "//" :language "odin")
+ (:comment "/*" :language "odin")
+ (:comment "*>" :language "cobol"))
+ "List of one-line comments organized by language.
+A language may have more than 1 comment string."
+ :group 'dumb-jump
+ :type
+ '(repeat
+ (plist
+ :options ((:comment string)
+ (:language string)))))
+
+(defun dumb-jump-get-comment-by-language (lang)
+ "Return a list of the one-line comment or comments for the given LANG."
+ (let* ((entries (seq-uniq
+ (seq-filter (lambda (it) (string= (plist-get it :language) lang))
+ dumb-jump-language-comments))))
+ (if (> (length entries) 0)
+ (mapcar (lambda (pl) (plist-get pl :comment)) entries)
+ nil)))
+
+(defun dumb-jump--context-startswith-comment (context-string comments)
+ "Return t when CONTEXT-STRING starts with any COMMENTS string."
+ (let ((startswith-comment nil)
+ ;; trim leading white-space from context-string
+ (context-str (replace-regexp-in-string "^\\s-+" "" context-string)))
+ (catch 'found-one
+ (dolist (comment comments)
+ (when (string-prefix-p comment context-str)
+ (setq startswith-comment t)
+ (throw 'found-one nil))))
+ startswith-comment))
+
+(defun dumb-jump-filter-no-start-comments (results lang)
+ "Filter out RESULTS with a :context starting with a LANG comment in file."
+ (let ((comments (dumb-jump-get-comment-by-language lang)))
+ (if comments
+ (seq-filter (lambda (it)
+ (not (dumb-jump--context-startswith-comment
+ (plist-get it :context)
+ comments)))
+ results)
+ results)))
+
+(defun dumb-jump-handle-results (results cur-file proj-root
+ ctx-type look-for
+ use-tooltip prefer-external
+ &optional language)
+ "Handle the searchers results.
+RESULTS is a list of property lists with the searcher's results.
+CUR-FILE is the current file within PROJ-ROOT.
+CTX-TYPE is a string of the current context.
+LOOK-FOR is the symbol we're jumping for.
+USE-TOOLTIP shows a preview instead of jumping.
+PREFER-EXTERNAL will sort current file last.
+LANGUAGE is an optional language to pass to `dumb-jump-process-results'."
+ (let* ((processed (dumb-jump-process-results results cur-file proj-root
+ ctx-type look-for use-tooltip
+ prefer-external language))
+ (results (plist-get processed :results))
+ (do-var-jump (plist-get processed :do-var-jump))
+ (var-to-jump (plist-get processed :var-to-jump))
+ (match-cur-file-front (plist-get processed :match-cur-file-front)))
+ (dumb-jump-debug-message
+ look-for
+ ctx-type
+ var-to-jump
+ (pp-to-string match-cur-file-front)
+ (pp-to-string results)
+ prefer-external
+ proj-root
+ cur-file)
+ (cond
+ (use-tooltip ;; quick-look mode
+ (let* ((choices (mapcar (lambda (it) (dumb-jump--format-result proj-root it)) results))
+ (selected-choice (dumb-jump--select-choice choices "Matches: "))
+ (selected-result (cdr (assoc selected-choice
+ (dumb-jump--zip-pair choices results)))))
+ (when selected-result
+ (dumb-jump--show-preview
+ (dumb-jump--format-result proj-root selected-result)))))
+ (do-var-jump
+ (dumb-jump-result-follow var-to-jump use-tooltip proj-root))
+ (t
+ (dumb-jump-prompt-user-for-choice proj-root match-cur-file-front)))))
+
+(defun dumb-jump--candidate-x<y (x y)
+ "Return non-nil if candidate X should sort before candidate Y.
+Order by :path length (shorter first), then by :line."
+ (let ((x-path-len (length (plist-get x :path)))
+ (y-path-len (length (plist-get y :path))))
+ (if (/= x-path-len y-path-len)
+ (< x-path-len y-path-len)
+ (< (plist-get x :line)
+ (plist-get y :line)))))
+
+(defun dumb-jump-process-results (results cur-file proj-root ctx-type
+ _look-for _use-tooltip
+ prefer-external &optional language)
+ "Process (filter, sort, ...) the searchers results.
+RESULTS is a list of property lists with the searcher's results.
+CUR-FILE is the current file within PROJ-ROOT.
+CTX-TYPE is a string of the current context.
+LOOK-FOR is the symbol we're jumping for.
+USE-TOOLTIP shows a preview instead of jumping.
+PREFER-EXTERNAL will sort current file last.
+LANGUAGE is the optional given language, if nil it will be found by
+`dumb-jump-get-language-by-filename'.
+Figure which of the RESULTS to jump to. Favoring the CUR-FILE."
+ (let* ((lang (if language language
+ (dumb-jump-get-language-by-filename cur-file)))
+ (match-sorted (seq-sort (lambda (x y)
+ (< (plist-get x :diff) (plist-get y :diff)))
+ results))
+ (match-no-comments (dumb-jump-filter-no-start-comments match-sorted
+ lang))
+
+ ;; Find the relative current file path by the project root.
+ ;; In some cases the results will not be absolute but relative and
+ ;; the "current file" filters must match in both cases. Also works
+ ;; when current file is in an arbitrary sub folder.
+ (rel-cur-file
+ (cond ((and (string-prefix-p proj-root cur-file)
+ (string-prefix-p default-directory cur-file))
+ (substring cur-file
+ (length default-directory)
+ (length cur-file)))
+
+ ((and (string-prefix-p proj-root cur-file)
+ (not (string-prefix-p default-directory cur-file)))
+ (substring cur-file
+ (1+ (length proj-root))
+ (length cur-file)))
+
+ (t
+ cur-file)))
+
+ ;; Moves current file results to the front of the list, unless
+ ;; PREFER-EXTERNAL then put them last.
+ (match-cur-file-front
+ (if (not prefer-external)
+ (append
+ (seq-filter (lambda (it)
+ (and (> (plist-get it :diff) 0)
+ (or (string= (plist-get it :path) cur-file)
+ (string= (plist-get it :path) rel-cur-file))))
+ match-no-comments)
+ (seq-filter (lambda (it)
+ (and (<= (plist-get it :diff) 0)
+ (or (string= (plist-get it :path) cur-file)
+ (string= (plist-get it :path) rel-cur-file))))
+ match-no-comments)
+ (seq-sort #'dumb-jump--candidate-x<y
+ (seq-filter (lambda (it)
+ (not (or (string= (plist-get it :path) cur-file)
+ (string= (plist-get it :path) rel-cur-file))))
+ match-no-comments)))
+ (append
+ (seq-sort #'dumb-jump--candidate-x<y
+ (seq-filter (lambda (it)
+ (not (or (string= (plist-get it :path) cur-file)
+ (string= (plist-get it :path) rel-cur-file))))
+ match-no-comments))
+ (seq-filter (lambda (it)
+ (or (string= (plist-get it :path) cur-file)
+ (string= (plist-get it :path) rel-cur-file)))
+ match-no-comments))))
+
+ (matches
+ (if (not prefer-external)
+ (seq-uniq
+ (append
+ (dumb-jump-current-file-results cur-file match-cur-file-front)
+ (dumb-jump-current-file-results rel-cur-file match-cur-file-front)))
+ match-cur-file-front))
+
+ (var-to-jump (car matches))
+ ;; TODO: handle if ctx-type is null but ALL results are variable
+
+ ;; When non-aggressive it should only jump when there is only one
+ ;; match, regardless of context.
+ (do-var-jump
+ (and (or dumb-jump-aggressive
+ (= (length match-cur-file-front) 1))
+ (or (= (length matches) 1)
+ (string= ctx-type "variable")
+ (string= ctx-type ""))
+ var-to-jump)))
+
+ (list :results results
+ :do-var-jump do-var-jump
+ :var-to-jump var-to-jump
+ :match-cur-file-front match-cur-file-front)))
+
+(declare-function tramp-file-name-localname "tramp")
+(declare-function tramp-dissect-file-name "tramp")
+
+(defun dumb-jump-read-config (root config-file)
+ "Load and return options (exclusions, inclusions, etc).
+Ffrom the ROOT project CONFIG-FILE."
+ (with-temp-buffer
+ (insert-file-contents (expand-file-name config-file root))
+ (let ((local-root (if (file-remote-p root)
+ (progn
+ (require 'tramp)
+ (tramp-file-name-localname
+ (tramp-dissect-file-name root)))
+ root))
+ include exclude lang)
+ (while (not (eobp))
+ (cond ((looking-at "^language \\\(.+\\\)")
+ (setq lang (match-string 1)))
+ ((looking-at "^\\+\\(.+\\)")
+ (push (expand-file-name (match-string 1) local-root)
+ include))
+ ((looking-at "^-/?\\(.+\\)")
+ (push (expand-file-name (match-string 1) local-root)
+ exclude)))
+ (forward-line))
+ (list :exclude (nreverse exclude)
+ :include (nreverse include)
+ :language lang))))
+
+(defun dumb-jump-file-modified-p (path)
+ "Return non-nil if file at PATH is open in Emacs and has a modified buffer."
+ (seq-some (lambda (it)
+ (and (buffer-modified-p it)
+ (buffer-file-name it)
+ (file-exists-p (buffer-file-name it))
+ (file-equal-p (buffer-file-name it) path)))
+ (buffer-list)))
+
+(defun dumb-jump-result-follow (result &optional use-tooltip proj)
+ "Jump to location identified by RESULT.
+Before jumping, record the jump to be able to jump back later.
+
+If destination buffer was modified, prompt user to proceed if the
+`dumb-jump-confirm-jump-to-modified-file' user-option is non-nil,
+otherwise display a warning.
+
+With optional USE-TOOLTIP non-nil, pop a tool-tip showing the project
+PROJ and RESULT information."
+ (if (dumb-jump-file-modified-p (plist-get result :path))
+ (let ((target-file (plist-get result :path)))
+ (if dumb-jump-confirm-jump-to-modified-file
+ (when (y-or-n-p
+ (concat
+ target-file
+ " has been modified so we may have the wrong location. Continue?"))
+ (dumb-jump--result-follow result use-tooltip proj))
+ (message
+ "Warning: %s has been modified so we may have the wrong location."
+ target-file)
+ (dumb-jump--result-follow result use-tooltip proj)))
+ (dumb-jump--result-follow result use-tooltip proj)))
+
+(defun dumb-jump--result-follow (result &optional use-tooltip proj)
+ "Jump to location identified by RESULT.
+Before jumping, record the jump to be able to jump back later.
+
+With optional USE-TOOLTIP non-nil, pop a tool-tip showing the project
+PROJ and RESULT information."
+ (let* ((target-boundary (dumb-jump--matched-positions-all
+ (concat "\\b"
+ (regexp-quote (plist-get result :target))
+ "\\b")
+ (plist-get result :context)))
+ ;; column position is either via tpos from ag or by using the regex
+ ;; above or last using old dumb-jump--index-of
+ (column (if target-boundary
+ (car (car target-boundary))
+ (dumb-jump--index-of (plist-get result :target)
+ (plist-get result :context))))
+
+ (result-path (plist-get result :path))
+
+ ;; Return value is either a string like "/ssh:user@1.2.3.4:" or nil
+ (tramp-path-prefix (file-remote-p default-directory))
+
+ ;; If result-path is an absolute path, the prefix is added to the head of it,
+ ;; or result-path is added to the end of default-directory
+ (path-for-tramp (when (and result-path tramp-path-prefix)
+ (if (file-name-absolute-p result-path)
+ (concat tramp-path-prefix result-path)
+ (concat default-directory result-path))))
+
+ (thef (or path-for-tramp result-path))
+ (line (plist-get result :line)))
+ (when thef
+ (if use-tooltip
+ (dumb-jump--show-preview (dumb-jump--format-result proj result))
+ (dumb-jump-goto-file-line thef line column)))
+ ;; return the file for test
+ thef))
+
+(defun dumb-jump-goto-file-line (thefile theline column)
+ "Open THEFILE, move point to line THELINE at COLUMN.
+Push current position to the xref marker stack.
+Run the `dumb-jump-before-jump-hook' hooks before jumping and
+the `dumb-jump-after-jump-hook' hooks after the jump."
+ (xref-push-marker-stack)
+ ;; Run the before-jump hooks
+ (with-demoted-errors "Error running `dumb-jump-before-jump-hook': %S"
+ (run-hooks 'dumb-jump-before-jump-hook))
+ ;; Open THEFILE in a buffer and a window specified by user-options.
+ (let* ((visible-buffer (find-buffer-visiting thefile))
+ (visible-window (when visible-buffer
+ (get-buffer-window visible-buffer))))
+ (cond
+ ((and visible-window dumb-jump-use-visible-window)
+ (select-window visible-window))
+ ((eq dumb-jump-window 'other)
+ (find-file-other-window thefile))
+ (t (find-file thefile))))
+ ;; Move point to THELINE at COLUMN
+ (goto-char (point-min))
+ (forward-line (1- theline))
+ (forward-char column)
+ ;; Run the after-jump hooks
+ (with-demoted-errors "Error running `dumb-jump-after-jump-hook': %S"
+ (run-hooks 'dumb-jump-after-jump-hook)))
+
+(defun dumb-jump-current-file-results (path results)
+ "Return the PATH's RESULTS."
+ (let ((matched (seq-filter (lambda (it) (string= path (plist-get it :path))) results)))
+ matched))
+
+(defun dumb-jump-generators-by-searcher (searcher)
+ "Return action property list for specific SEARCHER.
+
+- SEARCHER: symbol: the name of the search tool.
+ One of: git-grep, ag, git-grep-plus-ag, rg, gnu-grep, grep.
+The returned plist has:
+- :parse : function: response parser.
+- :generate : function: search command string generator. These functions
+ accept 6 arguments: look-for, cur-file, proj,
+ regexes, lang, exclude-paths.
+- :installed : predicate installed? function: is searcher installed?
+- :searcher : symbol : the SEARCHER (see above)."
+ (cond ((equal 'git-grep searcher)
+ `(:parse ,'dumb-jump-parse-git-grep-response
+ :generate ,'dumb-jump-generate-git-grep-command
+ :installed ,'dumb-jump-git-grep-installed?
+ :searcher ,searcher))
+ ((equal 'ag searcher)
+ `(:parse ,'dumb-jump-parse-ag-response
+ :generate ,'dumb-jump-generate-ag-command
+ :installed ,'dumb-jump-ag-installed?
+ :searcher ,searcher))
+ ((equal 'git-grep-plus-ag searcher)
+ `(:parse ,'dumb-jump-parse-ag-response
+ :generate ,'dumb-jump-generate-git-grep-plus-ag-command
+ :installed ,'dumb-jump-git-grep-plus-ag-installed?
+ :searcher ,searcher))
+ ((equal 'rg searcher)
+ `(:parse ,'dumb-jump-parse-rg-response
+ :generate ,'dumb-jump-generate-rg-command
+ :installed ,'dumb-jump-rg-installed?
+ :searcher ,searcher))
+ ((equal 'gnu-grep searcher)
+ `(:parse ,'dumb-jump-parse-grep-response
+ :generate ,'dumb-jump-generate-gnu-grep-command
+ :installed ,'dumb-jump-grep-installed?
+ :searcher ,searcher))
+ ((equal 'grep searcher)
+ `(:parse ,'dumb-jump-parse-grep-response
+ :generate ,'dumb-jump-generate-grep-command
+ :installed ,'dumb-jump-grep-installed?
+ :searcher ,searcher))))
+
+(defun dumb-jump-selected-grep-variant (&optional proj-root)
+ "Return search tool for current project or specified PROJ-ROOT.
+Select search tool according to `dumb-jump-prefer-searcher' choice and its
+possible `dumb-jump-force-searcher' overriding."
+ (let ((searcher-found nil))
+ (cond
+ ;; If `dumb-jump-force-searcher' is not nil then use the searcher
+ ;; identified by that user-option if possible.
+ (dumb-jump-force-searcher
+ (cond
+ ;; Honour forced choices, they might be identified in dir-local file
+ ((member dumb-jump-force-searcher '(ag
+ rg
+ grep
+ gnu-grep
+ git-grep
+ git-grep-plus-ag))
+ (setq searcher-found dumb-jump-force-searcher))
+ ;; Select search tool identified by calling the user-specified function
+ ;; which takes the directory.
+ ((and (symbolp dumb-jump-force-searcher)
+ (fboundp dumb-jump-force-searcher))
+ (let ((user-selected-search-tool
+ (funcall dumb-jump-force-searcher (or proj-root
+ (buffer-file-name)))))
+ (when user-selected-search-tool
+ (setq searcher-found user-selected-search-tool))))
+ ;; Check if project root or current directory is inside one of the
+ ;; project roots specified
+ ((and (listp dumb-jump-force-searcher)
+ proj-root
+ (dumb-jump-git-grep-installed?)
+ (member (dumb-jump-get-project-root proj-root)
+ dumb-jump-force-searcher)
+ (file-exists-p (expand-file-name ".git" proj-root)))
+ ;; [:todo 2026-01-07, by Pierre Rouleau: add support for git grep + ag??]
+ (setq searcher-found 'git-grep)))))
+
+ (unless searcher-found
+ ;; When `dumb-jump-force-searcher' does not override the selection, then
+ ;; select the searcher based on the value of `dumb-jump-force-searcher'.
+ (cond
+ ;; If `dumb-jump-prefer-searcher' identifies a searcher use it if it's
+ ;; installed.
+ ((and dumb-jump-prefer-searcher
+ (funcall (plist-get (dumb-jump-generators-by-searcher
+ dumb-jump-prefer-searcher)
+ :installed)))
+ (setq searcher-found dumb-jump-prefer-searcher))
+
+ ;; Fallback searcher order.
+ ((dumb-jump-ag-installed?) (setq searcher-found 'ag))
+ ((dumb-jump-rg-installed?) (setq searcher-found 'rg))
+ ((eq (dumb-jump-grep-installed?) 'gnu) (setq searcher-found 'gnu-grep))
+ (t (setq searcher-found 'grep))))
+ ;; return the symbol identifying the selected search tool
+ searcher-found))
+
+(defun dumb-jump-pick-grep-variant (&optional proj-root)
+ "Get action search property list for current project or specified PROJ-ROOT.
+Select search tool according to `dumb-jump-prefer-searcher' choice and its
+possible `dumb-jump-force-searcher' overriding."
+ (dumb-jump-generators-by-searcher
+ (dumb-jump-selected-grep-variant proj-root)))
+
+(defun dumb-jump-shell-command-switch ()
+ "Return current shell command switch prevent loading shell profile/RC.
+Using that switch speeds shell command execution.
+The `shell-file-name' identifies the current shell."
+ (let ((base-name (downcase (file-name-base shell-file-name))))
+ (cond
+ ((or (string-equal "zsh" base-name)
+ (string-equal "csh" base-name)
+ (string-equal "tcsh" base-name))
+ "-icf")
+ ;;
+ ((string-equal "bash" base-name)
+ "-c")
+ ;;
+ (t
+ shell-command-switch))))
+
+(defconst dumb-jump--case-insensitive-languages
+ '("commonlisp"
+ "cobol")
+ "List of case insensitive languages.")
+
+;; TODO: rename dumb-jump-run-definition-command
+(defun dumb-jump-run-command (look-for proj regexes lang
+ exclude-args cur-file
+ line-num parse-fn generate-fn)
+ "Run the grep search command based on LOOK-FOR in the directory TOSEARCH.
+The parameters are:
+- LOOK-FOR: string: searched symbol
+- PROJ: string: directory path
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- LANG: string: handled language name specified in `dumb-jump-find-rules'.
+- EXCLUDE-ARGS:
+- CUR-FILE string: current file name.
+- LINE-NUM: integer: current line number.
+- PARSE-FN: the parse function; something like `dumb-jump-parse-grep-response'.
+- GENERATE-FN: the generate function;
+ something like `dumb-jump-generate-ag-command'."
+ (let* ((proj-root (if (file-remote-p proj)
+ (directory-file-name
+ (tramp-file-name-localname (tramp-dissect-file-name proj)))
+ proj))
+ (cmd (funcall generate-fn look-for cur-file proj-root regexes lang exclude-args))
+ (shell-command-switch (dumb-jump-shell-command-switch))
+ (rawresults ""))
+
+ (dumb-jump-debug-message cmd)
+ (setq rawresults (shell-command-to-string cmd))
+ (dumb-jump-debug-message rawresults)
+ (when (string-match-p "^PCRE2 is not available" rawresults)
+ (dumb-jump-message "dumb-jump: Ripgrep PCRE2 support is not available. Install ripgrep with PCRE2 support or customize `dumb-jump-rg-search-args' to remove \"--pcre2\".")
+ (dumb-jump-env-problem "Ripgrep PCRE2 support is not available in the current build."))
+ (when (and (string-blank-p rawresults)
+ dumb-jump-fallback-search
+ (not (eq dumb-jump--search-mode 'references)))
+ (setq regexes (list dumb-jump-fallback-regex))
+ (setq cmd (funcall generate-fn
+ look-for cur-file
+ proj-root regexes
+ lang exclude-args))
+ (dumb-jump-debug-message cmd)
+ (setq rawresults (shell-command-to-string cmd))
+ (dumb-jump-debug-message rawresults))
+ (unless (string-blank-p (or cmd ""))
+ (let ((results (funcall parse-fn rawresults cur-file line-num))
+ (ignore-case (member lang dumb-jump--case-insensitive-languages)))
+ (seq-filter (lambda (it)
+ (dumb-jump--contains-p look-for
+ (plist-get it :context) ignore-case))
+ results)))))
+
+(defun dumb-jump-parse-response-line (resp-line cur-file)
+ "Parse a search program's single RESP-LINE for CUR-FILE.
+Return a list of (path line context)."
+ (let* ((parts (seq-remove (lambda (it) (string= it ""))
+ (dumb-jump--split "\\(?:^\\|:\\)[0-9]+:" resp-line)))
+ (line-num-raw (dumb-jump--match "\\(?:^\\|:\\)\\([0-9]+\\):" resp-line))
+ (line-num (and line-num-raw
+ (> (length line-num-raw) 1)
+ (nth 1 line-num-raw))))
+
+ (cond
+ ;; fixes rare bug where context is blank but file is defined "/somepath/file.txt:14:"
+ ;; OR: (and (= (length parts) 1) (file-name-exists (nth 0 parts)))
+ ((dumb-jump--match ":[0-9]+:$" resp-line)
+ nil)
+ ((and parts line-num)
+ (if (= (length parts) 2)
+ (list (let ((path (expand-file-name (nth 0 parts))))
+ (if (file-name-absolute-p (nth 0 parts))
+ path
+ (file-relative-name path)))
+ line-num (nth 1 parts))
+ ; this case is when they are searching a particular file...
+ (list (let ((path (expand-file-name cur-file)))
+ (if (file-name-absolute-p cur-file)
+ path
+ (file-relative-name path)))
+ line-num (nth 0 parts)))))))
+
+;; --
+;; Parse Search Tool Results: indirectly called by `dumb-jump-run-command'
+;; -----------------------------------------------------------------------
+;; - `dumb-jump-parse-grep-response'
+;; - `dumb-jump-parse-ag-response'
+;; - `dumb-jump-parse-rg-response'
+;; - `dumb-jump-parse-git-grep-response'
+;; - `dumb-jump-parse-response-lines'
+
+(defun dumb-jump-parse-response-lines (parsed cur-file cur-line-num)
+ "Turn PARSED response lines into a list of property lists.
+Using CUR-FILE and CUR-LINE-NUM to exclude jump origin."
+ (let* ((records (mapcan
+ (lambda (it)
+ (when it
+ (let* ((line-num (string-to-number (nth 1 it)))
+ (diff (- cur-line-num line-num)))
+ (list `(:path ,(nth 0 it)
+ :line ,line-num
+ :context ,(nth 2 it)
+ :diff ,diff)))))
+ parsed))
+ (results (delq nil records)))
+ (seq-filter
+ (lambda (it)
+ (not (and
+ (string= (plist-get it :path) cur-file)
+ (= (plist-get it :line) cur-line-num))))
+ results)))
+
+(defun dumb-jump-parse-grep-response (resp cur-file cur-line-num)
+ "Parse raw grep search response RESP into a list of plists.
+- CUR-FILE: current file.
+- CUR-LINE-NUM: current line number."
+ (let* ((resp-no-warnings
+ (seq-filter (lambda (it)
+ (and (not (string-prefix-p "grep:" it))
+ (not (dumb-jump--contains-p "No such file or" it))))
+ (dumb-jump--split "\n" (dumb-jump--trim resp))))
+ (parsed (mapcar (lambda (it) (dumb-jump-parse-response-line it cur-file))
+ resp-no-warnings)))
+ (dumb-jump-parse-response-lines parsed cur-file cur-line-num)))
+
+(defun dumb-jump--clean-wsl-response (resp)
+ "Clean up RESP for Windows WSL/UNC path issues.
+Filters CMD.exe UNC path warnings and fixes /wsl$/ to //wsl$/."
+ (let* ((cleaned (replace-regexp-in-string
+ "CMD\\.EXE was started with the above path as the current directory\\.\nUNC paths are not supported\\..*?\n"
+ "" resp))
+ (fixed (replace-regexp-in-string "^/wsl\\$/" "//wsl$/" cleaned)))
+ (replace-regexp-in-string "\n/wsl\\$/" "\n//wsl$/" fixed)))
+
+(defun dumb-jump-parse-ag-response (resp cur-file cur-line-num)
+ "Parse raw ag search response RESP into a list of plists.
+- CUR-FILE: current file.
+- CUR-LINE-NUM: current line number."
+ (let* ((cleaned (dumb-jump--clean-wsl-response resp))
+ (resp-lines (dumb-jump--split "\n" (dumb-jump--trim cleaned)))
+ (parsed (mapcar (lambda (it) (dumb-jump-parse-response-line it cur-file))
+ resp-lines)))
+ (dumb-jump-parse-response-lines parsed cur-file cur-line-num)))
+
+(defun dumb-jump-parse-rg-response (resp cur-file cur-line-num)
+ "Parse raw rg search response RESP into a list of plists.
+- CUR-FILE: current file.
+- CUR-LINE-NUM: current line number."
+ (let* ((resp-lines (dumb-jump--split "\n" (dumb-jump--trim resp)))
+ (parsed (mapcar (lambda (it) (dumb-jump-parse-response-line it cur-file))
+ resp-lines)))
+ (dumb-jump-parse-response-lines parsed cur-file cur-line-num)))
+
+(defun dumb-jump-parse-git-grep-response (resp cur-file cur-line-num)
+ "Parse raw git grep response RESP into a list of plists.
+- CUR-FILE: current file.
+- CUR-LINE-NUM: current line number."
+ (let* ((resp-lines (dumb-jump--split "\n" (dumb-jump--trim resp)))
+ (parsed (mapcar (lambda (it) (dumb-jump-parse-response-line it cur-file))
+ resp-lines)))
+ (dumb-jump-parse-response-lines parsed cur-file cur-line-num)))
+
+;; --
+
+(defun dumb-jump-re-match (re s)
+ "Does regular expression RE match string S. If RE is nil return nil."
+ (when (and re s)
+ (dumb-jump--match re s)))
+
+(defun dumb-jump-get-ctx-type-by-language (lang pt-ctx)
+ "Detect the type of context by the language LANG and its context PT-CTX.
+PT-CTX is assumed to be a property list with the :left and :right attributes
+that are both strings."
+ (let* ((contexts (seq-filter (lambda (it)
+ (string= (plist-get it ':language) lang))
+ dumb-jump-language-contexts))
+ (usable-ctxs
+ (when (> (length contexts) 0)
+ (seq-filter (lambda (it)
+ (and (or (null (plist-get it :left))
+ (dumb-jump-re-match (plist-get it :left)
+ (plist-get pt-ctx :left)))
+ (or (null (plist-get it :right))
+ (dumb-jump-re-match (plist-get it :right)
+ (plist-get pt-ctx :right)))))
+ contexts)))
+ (use-ctx (= (length (seq-filter
+ (lambda (it)
+ (string= (plist-get it ':type)
+ (and usable-ctxs (plist-get (car usable-ctxs) :type))))
+ usable-ctxs))
+ (length usable-ctxs))))
+
+ (when (and usable-ctxs use-ctx)
+ (plist-get (car usable-ctxs) :type))))
+
+(defun dumb-jump-get-ext-includes (language)
+ "Return the --include grep argument of file extensions for LANGUAGE."
+ (let ((exts (dumb-jump-get-file-exts-by-language language)))
+ (dumb-jump-arg-joiner
+ "--include"
+ (mapcar (lambda (it) (format "\\*.%s" it)) exts))))
+
+(defun dumb-jump-arg-joiner (prefix values)
+ "Helper to generate command arg with its PREFIX for each value in VALUES."
+ (let* ((normalized-values (mapcar (lambda (it) (dumb-jump--trim (or it ""))) values))
+ (non-empty-values (seq-filter (lambda (it) (> (length it) 0)) normalized-values))
+ (args (dumb-jump--join (format " %s " prefix) non-empty-values)))
+ (if non-empty-values
+ (format " %s %s " prefix args)
+ "")))
+
+(defun dumb-jump-get-contextual-regexes (lang ctx-type searcher)
+ "Get list of search regular expressions by LANG and CTX-TYPE.
+The arguments are:
+- LANG: string: the language.
+- CTX-TYPE: variable, function, etc.
+- SEARCHER: symbol: the name of the search tool.
+ One of: git-grep, ag, git-grep-plus-ag, rg, gnu-grep, grep."
+ (let* ((raw-rules (dumb-jump-get-rules-by-language lang searcher))
+ (ctx-type (unless dumb-jump-ignore-context ctx-type))
+ (ctx-rules
+ (if ctx-type
+ (seq-filter (lambda (it) (string= (plist-get it :type) ctx-type)) raw-rules)
+ raw-rules))
+ (rules (or ctx-rules raw-rules))
+ (regexes (mapcar (lambda (it) (plist-get it :regex)) rules)))
+ regexes))
+
+(defun dumb-jump-populate-regex (it look-for variant)
+ "Populate IT regex template with LOOK-FOR for the specific search VARIANT.
+Replace the dumb-jump generic regular expression meta concepts with the
+one required for the search variant tool.
+The arguments are:
+- IT : string: the dumb-jump generic regular expression.
+- LOOK-FOR: string: the searched item.
+- VARIANT : symbol: the search tool variant,
+ one of: ag, rg, grep, gnu-grep, git-grep-plus-ag, git-grep."
+ (let ((boundary
+ (cond ((eq variant 'rg) dumb-jump-rg-word-boundary)
+ ((eq variant 'ag) dumb-jump-ag-word-boundary)
+ ((eq variant 'git-grep-plus-ag) dumb-jump-ag-word-boundary)
+ ((eq variant 'git-grep) dumb-jump-git-grep-word-boundary)
+ (t dumb-jump-grep-word-boundary))))
+ (let ((text it))
+ (setq text (dumb-jump--replace "\\j" boundary text))
+ (when (dumb-jump-use-space-bracket-exp-for variant)
+ (setq text (dumb-jump--replace "\\s" "[[:space:]]" text)))
+ (setq text (dumb-jump--replace "JJJ" (regexp-quote look-for) text))
+ text)))
+
+(defun dumb-jump-populate-regexes (look-for regexes variant)
+ "Return list of VARIANT specific regexes from the dumb-jump generic REGEXES.
+Update the generic regexes, populate the target identified in LOOK-FOR.
+The arguments are:
+- LOOK-FOR: string: the searched item.
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- VARIANT: symbol: the search tool variant,
+ one of: ag, rg, git-grep-plus-ag, git-grep."
+ (mapcar
+ (lambda (it)
+ (dumb-jump-populate-regex it look-for variant))
+ regexes))
+
+(defun dumb-jump-pcre-to-emacs (regex)
+ "Convert a PCRE-style REGEX string to Emacs regex syntax.
+In PCRE and Emacs, +, ?, *, ., ^, $, [ are special without escaping.
+But (, ), |, {, } differ: special unescaped in PCRE, special escaped in Emacs.
+This function swaps the literal/special meaning of (, ), |, {, and }."
+ (let ((i 0)
+ (len (length regex))
+ (out (list)))
+ (while (< i len)
+ (let ((ch (aref regex i)))
+ (cond
+ ;; Backslash sequence
+ ((and (= ch ?\\) (< (1+ i) len))
+ (let ((next (aref regex (1+ i))))
+ (cond
+ ((memq next '(?\( ?\) ?| ?{ ?}))
+ ;; \X (literal in PCRE) → X (literal in Emacs)
+ (push (char-to-string next) out)
+ (setq i (+ i 2)))
+ (t
+ ;; Other backslash sequences (\b, \w, \+, \?, etc.) pass through
+ (push (substring regex i (+ i 2)) out)
+ (setq i (+ i 2))))))
+ ;; (? prefixed groups: (?:, (?!, (?=, (?<=, (?<!, etc.
+ ;; Emit \(? which in Emacs starts a shy group for (?:
+ ;; and produces a valid (if semantically different) group for others.
+ ((and (= ch ?\()
+ (< (1+ i) len)
+ (= (aref regex (1+ i)) ??))
+ (push "\\(?" out)
+ (setq i (+ i 2)))
+ ;; Unescaped chars that differ between PCRE and Emacs
+ ((= ch ?\() (push "\\(" out) (cl-incf i))
+ ((= ch ?\)) (push "\\)" out) (cl-incf i))
+ ((= ch ?|) (push "\\|" out) (cl-incf i))
+ ((= ch ?{) (push "\\{" out) (cl-incf i))
+ ((= ch ?}) (push "\\}" out) (cl-incf i))
+ ;; Everything else passes through (including +, ?, *, ., etc.)
+ (t (push (char-to-string ch) out) (cl-incf i)))))
+ (apply #'concat (nreverse out))))
+
+(defun dumb-jump-populate-regex-for-emacs (regex look-for)
+ "Populate dumb-jump generic REGEX to match LOOK-FOR with `string-match-p'.
+Replaces dumb-jump meta-patterns and converts PCRE syntax to Emacs regex.
+The \\=\\j boundary is replaced after PCRE conversion with a character class
+matching the tool boundaries: not followed by [a-zA-Z0-9?*-]."
+ (let ((text regex))
+ (setq text (dumb-jump--replace "\\s" "[[:space:]]" text))
+ (setq text (dumb-jump--replace "JJJ" (regexp-quote look-for) text))
+ (setq text (dumb-jump-pcre-to-emacs text))
+ ;; Replace \j after PCRE conversion since the replacement is Emacs syntax.
+ ;; Mirrors the tool boundaries: (?![a-zA-Z0-9\?\*-]) for ag,
+ ;; ($|[^a-zA-Z0-9\?\*-]) for rg/grep.
+ (setq text (dumb-jump--replace "\\j" "\\(?:[^a-zA-Z0-9?*-]\\|$\\)" text))
+ text))
+
+(defun dumb-jump-filter-definition-results (results lang look-for _searcher)
+ "Remove from RESULTS any that match definition patterns for LANG.
+LOOK-FOR is the symbol being searched. _SEARCHER is unused but kept for API."
+ (let* ((raw-rules (seq-remove
+ (lambda (it) (plist-get it :skip-ref-filter))
+ (seq-filter (lambda (it)
+ (string= (plist-get it :language) lang))
+ dumb-jump-find-rules)))
+ (def-regexes (seq-uniq (mapcar (lambda (it) (plist-get it :regex)) raw-rules)))
+ (emacs-regexes (mapcar (lambda (re)
+ (dumb-jump-populate-regex-for-emacs re look-for))
+ def-regexes)))
+ (seq-filter
+ (lambda (r)
+ (let ((ctx (plist-get r :context)))
+ (not (seq-some (lambda (re)
+ (condition-case nil
+ (string-match-p re ctx)
+ (invalid-regexp nil)))
+ emacs-regexes))))
+ results)))
+
+;; --
+(defun dumb-jump-generate-ag-command (look-for
+ cur-file proj
+ regexes lang
+ exclude-paths)
+ "Return the ag command to search for LOOK-FOR in the PROJ directory.
+The arguments are:
+- LOOK-FOR: string: the searched item.
+- CUR-FILE: string: current file name. Used to check if it's compressed.
+- PROJ: string: project root path.
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- LANG: string: handled language.
+- EXCLUDE-PATHS: list of strings: directories to exclude from search."
+ (let* ((filled-regexes (dumb-jump-populate-regexes look-for regexes 'ag))
+ (agtypes (dumb-jump-get-ag-type-by-language lang))
+ (lang-exts (dumb-jump-get-file-exts-by-language lang))
+ (proj-dir (file-name-as-directory proj))
+ ;; TODO: --search-zip always? in case the include is the in gz area
+ ;; like emacs lisp code.
+ (cmd (concat dumb-jump-ag-cmd
+ " --nocolor --nogroup"
+ (if (member lang dumb-jump--case-insensitive-languages)
+ " --ignore-case"
+ "")
+ (if (string-suffix-p ".gz" cur-file)
+ " --search-zip"
+ "")
+ (unless (string-blank-p dumb-jump-ag-search-args)
+ (concat " " dumb-jump-ag-search-args))
+ (if agtypes
+ (dumb-jump--join "" (mapcar (lambda (it) (format " --%s" it)) agtypes))
+ (concat " -G '("
+ (dumb-jump--join "|"
+ (mapcar (lambda (it) (format "\\.%s" it)) lang-exts))
+ ")$'"))))
+ (exclude-args (dumb-jump-arg-joiner
+ "--ignore-dir"
+ (mapcar
+ (lambda (it)
+ (shell-quote-argument (dumb-jump--replace proj-dir "" it)))
+ exclude-paths)))
+ (regex-args (shell-quote-argument (dumb-jump--join "|" filled-regexes))))
+ (if (null regexes)
+ ""
+ (dumb-jump-concat-command cmd exclude-args "--" regex-args (shell-quote-argument proj)))))
+
+;; --
+(defun dumb-jump-get-git-grep-files-matching-symbol (symbol proj-root)
+ "Return a list of file names in PROJ-ROOT holding the literal SYMBOL.
+Search for matching files using git grep."
+ (let* ((cmd (format "git grep --full-name -F -c %s %s"
+ (shell-quote-argument symbol) (shell-quote-argument proj-root)))
+ (result (dumb-jump--trim (shell-command-to-string cmd)))
+ ;; result: '\n\ separated lines of: "path-name:count"
+ ;; extract the name of files and return them in a list.
+ (matched-files (mapcar
+ (lambda (it)
+ (cl-first (dumb-jump--split ":" it)))
+ (dumb-jump--split "\n" result))))
+ matched-files))
+
+(defun dumb-jump-format-files-as-ag-arg (files proj-root)
+ "Take a list of FILES and their PROJ-ROOT and return a `ag -G` argument."
+ (format "'(%s)'" (dumb-jump--join "|" (mapcar (lambda (it)
+ (file-relative-name
+ (expand-file-name it proj-root)))
+ files))))
+
+(defun dumb-jump-get-git-grep-files-matching-symbol-as-ag-arg (symbol proj-root)
+ "Return optimized `ag -G` argument string to search for SYMBOL in PROJ-ROOT.
+Limit the ag search to files matching SYMBOL in PROJ-ROOT by first getting the
+list of files with `git grep`.
+The string does not contain the `ag -G`, just its argument."
+ (dumb-jump-format-files-as-ag-arg
+ (dumb-jump-get-git-grep-files-matching-symbol symbol proj-root)
+ proj-root))
+
+
+;; git-grep plus ag only recommended for huge repos like the linux kernel.
+(defun dumb-jump-generate-git-grep-plus-ag-command (look-for
+ cur-file proj
+ regexes lang
+ exclude-paths)
+ "Return the ag command to search for LOOK-FOR in the PROJ directory.
+Using ag to search only the files found via git-grep literal symbol search.
+Note: Only recommended for huge repos like the linux kernel.
+The arguments are:
+- LOOK-FOR: string: the searched text
+- CUR-FILE:
+- PROJ: string: project root path.
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- LANG: string: handled language.
+- EXCLUDE_PATHS: list of strings: directories to exclude from search."
+ (let* ((filled-regexes (dumb-jump-populate-regexes look-for regexes 'ag))
+ (proj-dir (file-name-as-directory proj))
+ (ag-files-arg
+ (dumb-jump-get-git-grep-files-matching-symbol-as-ag-arg
+ look-for proj-dir))
+ (cmd (concat dumb-jump-ag-cmd
+ " --nocolor --nogroup"
+ (if (member lang dumb-jump--case-insensitive-languages)
+ " --ignore-case"
+ "")
+ (if (string-suffix-p ".gz" cur-file)
+ " --search-zip"
+ "")
+ " -G " ag-files-arg
+ " "))
+ (exclude-args (dumb-jump-arg-joiner
+ "--ignore-dir"
+ (mapcar
+ (lambda (it)
+ (shell-quote-argument (dumb-jump--replace proj-dir "" it)))
+ exclude-paths)))
+ (regex-args (shell-quote-argument (dumb-jump--join "|" filled-regexes))))
+ (if (null regexes)
+ ""
+ (dumb-jump-concat-command cmd exclude-args regex-args (shell-quote-argument proj)))))
+
+;; --
+(defun dumb-jump-generate-rg-command (look-for
+ _cur-file proj
+ regexes lang
+ exclude-paths)
+ "Return the rg command to search for LOOK-FOR in the PROJ directory.
+The arguments are:
+- LOOK-FOR: string: the searched item.
+- CUR-FILE:
+- PROJ: string: project root path.
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- LANG: string: handled language.
+- EXCLUDE-PATHS: list of strings: directories to exclude from search."
+ (let* ((filled-regexes (dumb-jump-populate-regexes look-for regexes 'rg))
+ (rgtypes (dumb-jump-get-rg-type-by-language lang))
+ (rg-nontype-exts (dumb-jump-get-rg-nontype-exts-by-language lang))
+ (proj-dir (file-name-as-directory proj))
+ (cmd (concat dumb-jump-rg-cmd
+ " --color never --no-heading --line-number -U"
+ (if (member lang dumb-jump--case-insensitive-languages)
+ " --ignore-case"
+ "")
+ (unless (string-blank-p dumb-jump-rg-search-args)
+ (concat " " dumb-jump-rg-search-args))
+ (dumb-jump--join "" (mapcar (lambda (it) (format " --type %s" it)) rgtypes))
+ (if rgtypes ""
+ (dumb-jump--join "" (mapcar (lambda (it) (format " -g \"*.%s\"" it)) rg-nontype-exts)))))
+ (exclude-args (dumb-jump-arg-joiner
+ "-g"
+ (mapcar
+ (lambda (it)
+ (shell-quote-argument
+ (concat "!" (dumb-jump--replace proj-dir "" it))))
+ exclude-paths)))
+ (regex-args (shell-quote-argument (dumb-jump--join "|" filled-regexes))))
+ (if (null regexes)
+ ""
+ (dumb-jump-concat-command cmd exclude-args "--" regex-args (shell-quote-argument proj)))))
+
+(defun dumb-jump-generate-git-grep-command (look-for
+ cur-file proj
+ regexes lang
+ exclude-paths)
+ "Return the git grep command to search for LOOK-FOR in the PROJ directory.
+The arguments are:
+- LOOK-FOR: string: the searched text
+- CUR-FILE: string: current file name. Used to check the file extension and
+ restrict search to file extensions of the matching LANG.
+- PROJ: string: project root path.
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- LANG: string: handled language.
+- EXCLUDE_PATHS:list of strings: directories to exclude from search."
+ (let* ((filled-regexes (dumb-jump-populate-regexes look-for regexes 'git-grep))
+ (ggtypes (when (file-name-extension cur-file)
+ (dumb-jump-get-git-grep-type-by-language lang)))
+ (cmd (concat dumb-jump-git-grep-cmd
+ " --color=never --line-number"
+ (if (member lang dumb-jump--case-insensitive-languages)
+ " --ignore-case"
+ "")
+ (when dumb-jump-git-grep-search-untracked
+ " --untracked")
+ (unless (string-blank-p dumb-jump-git-grep-search-args)
+ (concat " " dumb-jump-git-grep-search-args))
+ " -E"))
+ (fileexps (dumb-jump--join " "
+ (or
+ (mapcar (lambda (it)
+ (shell-quote-argument
+ (format "%s/*.%s" proj it)))
+ ggtypes)
+ '(":/"))))
+ (exclude-args (dumb-jump--join " "
+ (mapcar (lambda (it)
+ (shell-quote-argument
+ (concat ":(exclude)" it)))
+ exclude-paths)))
+ (regex-args (shell-quote-argument (dumb-jump--join "|" filled-regexes))))
+ (if (null regexes)
+ ""
+ (dumb-jump-concat-command cmd regex-args "--" fileexps exclude-args))))
+
+(defun dumb-jump-generate-grep-command (look-for
+ cur-file proj
+ regexes lang
+ exclude-paths)
+ "Return the grep command to search for LOOK-FOR in PROJ directory.
+The arguments are:
+- LOOK-FOR: string: the searched item.
+- CUR-FILE: string: current file name. Used to check if it's compressed.
+- PROJ: string: project root path.
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- LANG: string: handled language.
+- EXCLUDE-PATHS: list of strings: directories to exclude from search."
+ (let* ((filled-regexes
+ (mapcar (lambda (it) (shell-quote-argument it))
+ (dumb-jump-populate-regexes look-for regexes 'grep)))
+ (cmd (concat
+ (if (eq system-type 'windows-nt)
+ ""
+ (concat dumb-jump-grep-prefix " "))
+ (if (string-suffix-p ".gz" cur-file)
+ dumb-jump-zgrep-cmd
+ dumb-jump-grep-cmd)))
+ (case-args (if (member lang dumb-jump--case-insensitive-languages)
+ " --ignore-case"
+ ""))
+ (proj-dir (file-name-as-directory proj))
+ (exclude-args (dumb-jump-arg-joiner
+ "--exclude-dir"
+ (mapcar
+ (lambda (it)
+ (dumb-jump--replace proj-dir "" it))
+ exclude-paths)))
+ (include-args (dumb-jump-get-ext-includes lang))
+ (regex-args (dumb-jump-arg-joiner "-e" filled-regexes)))
+ (if (null regexes)
+ ""
+ (dumb-jump-concat-command cmd dumb-jump-grep-args
+ case-args exclude-args
+ include-args regex-args (shell-quote-argument proj)))))
+
+(defun dumb-jump-generate-gnu-grep-command (look-for
+ cur-file proj
+ regexes lang
+ _exclude-paths)
+ "Return the GNU grep command to search for LOOK-FOR in PROJ directory.
+The arguments are:
+- LOOK-FOR: string: the searched item.
+- CUR-FILE: string: current file name. Used to check if it's compressed.
+- PROJ: string: project root path.
+- REGEXES: list of strings: each string is a dumb-jump generic regular
+ expression.
+- LANG: string: handled language.
+- EXCLUDE-PATHS: list of strings: directories to exclude from search."
+ (let* ((filled-regexes
+ (mapcar (lambda (it) (shell-quote-argument it))
+ (dumb-jump-populate-regexes look-for regexes 'gnu-grep)))
+ (cmd (concat
+ (if (eq system-type 'windows-nt)
+ ""
+ (concat dumb-jump-grep-prefix " "))
+ (if (string-suffix-p ".gz" cur-file)
+ dumb-jump-zgrep-cmd
+ dumb-jump-grep-cmd)))
+ (case-args (if (member lang dumb-jump--case-insensitive-languages)
+ " --ignore-case"
+ ""))
+ ;; TODO: GNU grep doesn't support these, so skip them
+ (exclude-args "")
+ (include-args "")
+ (regex-args (dumb-jump-arg-joiner "-e" filled-regexes)))
+ (if (null regexes)
+ ""
+ (dumb-jump-concat-command cmd dumb-jump-gnu-grep-args
+ case-args exclude-args
+ include-args regex-args (shell-quote-argument proj)))))
+
+;; ---------------------------------------------------------------------------
+
+(defun dumb-jump-concat-command (&rest parts)
+ "Concat the PARTS of a command if each part has a length."
+ (let* ((normalized-parts (mapcar (lambda (it) (dumb-jump--trim (or it ""))) parts))
+ (non-empty-parts (seq-filter (lambda (it) (> (length it) 0)) normalized-parts)))
+ (dumb-jump--join " " non-empty-parts)))
+
+(defun dumb-jump-get-file-exts-by-language (language)
+ "Return list of file extensions for a LANGUAGE."
+ (mapcar (lambda (it) (plist-get it :ext))
+ (seq-filter (lambda (it) (string= (plist-get it :language) language))
+ dumb-jump-language-file-exts)))
+
+(defun dumb-jump-get-ag-type-by-language (language)
+ "Return list of ag type argument for a LANGUAGE."
+ (seq-uniq (mapcar (lambda (it) (plist-get it :agtype))
+ (seq-filter (lambda (it)
+ (and
+ (plist-get it :agtype)
+ (string= (plist-get it :language) language)))
+ dumb-jump-language-file-exts))))
+
+(defun dumb-jump-get-rg-type-by-language (language)
+ "Return list of rg type argument for a LANGUAGE."
+ (seq-uniq (mapcar (lambda (it) (plist-get it :rgtype))
+ (seq-filter (lambda (it)
+ (and
+ (plist-get it :rgtype)
+ (string= (plist-get it :language) language)))
+ dumb-jump-language-file-exts))))
+
+(defun dumb-jump-get-rg-nontype-exts-by-language (language)
+ "Return list of file extensions for a LANGUAGE that have no rgtype.
+These extensions need to be explicitly added as glob patterns since
+they are not covered by ripgrep's built-in type definitions."
+ (seq-uniq (mapcar (lambda (it) (plist-get it :ext))
+ (seq-filter (lambda (it)
+ (and
+ (plist-get it :ext)
+ (null (plist-get it :rgtype))
+ (string= (plist-get it :language) language)))
+ dumb-jump-language-file-exts))))
+
+(defun dumb-jump-get-git-grep-type-by-language (language)
+ "Return list of git grep type argument for a LANGUAGE."
+ (seq-uniq (mapcar (lambda (it) (plist-get it :ext))
+ (seq-filter (lambda (it)
+ (and
+ (plist-get it :ext)
+ (string= (plist-get it :language) language)))
+ dumb-jump-language-file-exts))))
+
+(defun dumb-jump-get-rules-by-language (language searcher)
+ "Return a list of rules for the LANGUAGE by SEARCHER."
+ (let* ((searcher-str (cond ((eq 'git-grep searcher) "git-grep")
+ ((eq 'rg searcher) "rg")
+ ((eq 'ag searcher) "ag")
+ ((eq 'git-grep-plus-ag searcher) "ag")
+ (t "grep")))
+ (results (seq-filter (lambda (it)
+ (and
+ (string= (plist-get it ':language) language)
+ (member searcher-str (plist-get it ':supports))))
+ dumb-jump-find-rules)))
+ (if dumb-jump-functions-only
+ (seq-filter (lambda (it) (string= (plist-get it ':type) "function")) results)
+ results)))
+
+;;;###autoload
+(define-minor-mode dumb-jump-local-mode
+ "Buffer-local minor mode for jumping to variable and function definitions.
+Use this to selectively enable dumb-jump in specific major modes, e.g.:
+ (add-hook \\='python-mode-hook #\\='dumb-jump-local-mode)
+For enabling globally, use `dumb-jump-mode' instead."
+ :global nil
+ :keymap dumb-jump-mode-map)
+
+;;;###autoload
+(define-minor-mode dumb-jump-mode
+ "Minor mode for jumping to variable and function definitions."
+ ;; Note: independent of the Emacs xref interface.
+ :global t
+ :keymap dumb-jump-mode-map)
+
+
+;; -----------------------------------------------------------------------------
+;;; Xref Backend
+
+(unless dumb-jump-disable-obsolete-warnings
+ (dolist (obsolete
+ '(dumb-jump-mode
+ dumb-jump-go
+ dumb-jump-go-prefer-external-other-window
+ dumb-jump-go-prompt
+ dumb-jump-quick-look
+ dumb-jump-go-other-window
+ dumb-jump-go-current-window
+ dumb-jump-go-prefer-external))
+ (make-obsolete
+ obsolete
+ (format "`%s' has been obsoleted by the xref interface."
+ obsolete)
+ "2020-06-26"))
+ (make-obsolete 'dumb-jump-back
+ "`dumb-jump-back' has been obsoleted by `xref-pop-marker-stack'."
+ "2020-06-26"))
+
+(cl-defmethod xref-backend-identifier-at-point ((_backend (eql dumb-jump)))
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (and bounds (let* ((ident (dumb-jump-get-point-symbol))
+ (start (car bounds))
+ (col (- start (line-beginning-position)))
+ (line (dumb-jump-get-point-line))
+ (ctx (dumb-jump-get-point-context line ident col)))
+ (propertize ident :dumb-jump-ctx ctx)))))
+
+(cl-defmethod xref-backend-definitions ((_backend (eql dumb-jump))
+ entered-name)
+ (let* ((info (dumb-jump-get-results entered-name))
+ (results (plist-get info :results))
+ (look-for (or entered-name (plist-get info :symbol)))
+ (proj-root (plist-get info :root))
+ (issue (plist-get info :issue))
+ (lang (plist-get info :lang))
+ (processed (dumb-jump-process-results
+ results
+ (plist-get info :file)
+ proj-root
+ (plist-get info :ctx-type)
+ look-for
+ nil
+ nil
+ lang))
+ (results (plist-get processed :results))
+ (do-var-jump (plist-get processed :do-var-jump))
+ (var-to-jump (plist-get processed :var-to-jump))
+ (match-cur-file-front (plist-get processed :match-cur-file-front)))
+
+ (dumb-jump-debug-message
+ look-for
+ (plist-get info :ctx-type)
+ var-to-jump
+ (pp-to-string match-cur-file-front)
+ (pp-to-string results)
+ match-cur-file-front
+ proj-root
+ (plist-get info :file))
+ (cond ((eq issue 'nogrep)
+ (dumb-jump-message "Please install ag, rg, git grep or grep!"))
+ ((eq issue 'nosymbol)
+ (dumb-jump-message "No symbol under point."))
+ ((string-suffix-p " file" lang)
+ (dumb-jump-message "Could not find rules for '%s'." lang))
+ ((= (length results) 0)
+ (dumb-jump-message "'%s' %s %s declaration not found."
+ look-for
+ (if (string-blank-p (or lang ""))
+ "with unknown language so"
+ lang)
+ (plist-get info :ctx-type)))
+ (t (mapcar (lambda (res)
+ (with-no-warnings
+ (xref-make
+ (plist-get res :context)
+ (xref-make-file-location
+ (let* ((path (plist-get res :path))
+ (remote-prefix (file-remote-p default-directory)))
+ (if remote-prefix
+ (if (file-name-absolute-p path)
+ (concat remote-prefix path)
+ (concat default-directory path))
+ (expand-file-name path)))
+ (plist-get res :line)
+ 0))))
+ (if do-var-jump
+ (list var-to-jump)
+ match-cur-file-front))))))
+
+(cl-defmethod xref-backend-references ((_backend (eql dumb-jump))
+ entered-name)
+ (let* ((dumb-jump--search-mode 'references)
+ (info (dumb-jump-get-results entered-name))
+ (results (plist-get info :results))
+ (look-for (or entered-name (plist-get info :symbol)))
+ (proj-root (plist-get info :root))
+ (issue (plist-get info :issue))
+ (lang (plist-get info :lang))
+ (processed (dumb-jump-process-results
+ results
+ (plist-get info :file)
+ proj-root
+ (plist-get info :ctx-type)
+ look-for
+ nil
+ nil
+ lang))
+ (results (plist-get processed :results))
+ (match-cur-file-front (plist-get processed :match-cur-file-front)))
+
+ (cond ((eq issue 'nogrep)
+ (dumb-jump-message "Please install ag, rg, git grep or grep!"))
+ ((eq issue 'nosymbol)
+ (dumb-jump-message "No symbol under point."))
+ ((string-suffix-p " file" lang)
+ (dumb-jump-message "Could not find rules for '%s'." lang))
+ ((= (length results) 0)
+ (dumb-jump-message "'%s' %s references not found."
+ look-for
+ (if (string-blank-p (or lang ""))
+ "with unknown language so"
+ lang)))
+ (t (mapcar (lambda (res)
+ (with-no-warnings
+ (xref-make
+ (plist-get res :context)
+ (xref-make-file-location
+ (let* ((path (plist-get res :path))
+ (remote-prefix (file-remote-p default-directory)))
+ (if remote-prefix
+ (if (file-name-absolute-p path)
+ (concat remote-prefix path)
+ (concat default-directory path))
+ (expand-file-name path)))
+ (plist-get res :line)
+ 0))))
+ match-cur-file-front)))))
+
+(cl-defmethod xref-backend-apropos ((_backend (eql dumb-jump)) pattern)
+ (xref-backend-definitions 'dumb-jump pattern))
+
+(cl-defmethod xref-backend-identifier-completion-table
+ ((_backend (eql dumb-jump)))
+ nil)
+
+;;;###autoload
+(defun dumb-jump-xref-activate ()
+ "Function to activate xref backend.
+Add this function to `xref-backend-functions' to dumb jump to be
+activated, whenever it finds a project. It is recommended to add
+it to the end, so that it only gets activated when no better
+option is found."
+ (and (dumb-jump-get-project-root default-directory)
+ 'dumb-jump))
+
+(provide 'dumb-jump)
+;;; dumb-jump.el ends here