From 3f4e5a930f8ac19846468887af4805a67f206449 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 26 Sep 2026 05:14:11 +0700 Subject: [PATCH] A defining form written as the fallback call in a .fln buffer draws its head as a keyword and its name as a definition, and imenu lists it. --- emacs/flan-fln-mode.el | 39 ++++++++++++++++++++++++++++++++++++++- emacs/test-flan-fln.el | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/emacs/flan-fln-mode.el b/emacs/flan-fln-mode.el index a27a1000..8fbde9c8 100644 --- a/emacs/flan-fln-mode.el +++ b/emacs/flan-fln-mode.el @@ -1526,6 +1526,29 @@ it, so a block pasted at another depth stays one block." (defconst flan-fln--name-re "\\([^][ \t\n(){},;\":]+\\)" "A declared name: a run up to a bracket, a space, or the colon of `x: T'.") +;; A defining form written as the fallback call, `defmacro(m, [x]):' or +;; `defmethod(area, point, [p]):'. The heads are `flan-mode''s own, so a +;; head it learns is drawn here too; its name is drawn as the sugar draws the +;; same kind of name. +(defconst flan-fln--fallback-type-heads + (seq-filter (lambda (h) (member h '("defstruct" "defdata" "defunion" "defenum" + "defalias" "defclass"))) + flan--definers)) + +(defconst flan-fln--fallback-variable-heads + (seq-filter (lambda (h) (member h '("def" "defonce" "defconst"))) flan--definers)) + +(defconst flan-fln--fallback-function-heads + (seq-remove (lambda (h) (or (member h flan-fln--fallback-type-heads) + (member h flan-fln--fallback-variable-heads) + (member h '("defmacro" "import" "package" + "declare" "declare-c")))) + flan--definers) + "The fallback heads that define something called, for imenu.") + +(defun flan-fln--fallback-re (heads) + (concat "^" (regexp-opt heads t) "(" flan-fln--name-re)) + (defun flan-fln--return-type-matcher (limit) "Find the next return type up to LIMIT: after the `->' of a fn header, a lambda or a `Fn(...)' type, and not after a match arm's." @@ -1565,6 +1588,17 @@ lambda or a `Fn(...)' type, and not after a match arm's." ;; An alias's type, `type Row = Vec(i32)'. (,(concat "^type[ \t]+[^][ \t\n(){},;\":]+[ \t]+=[ \t]+" flan-fln--name-re) 1 font-lock-type-face) + ;; A defining form as the fallback call: the head a keyword, its first + ;; argument the name it defines. + (,(flan-fln--fallback-re flan-fln--fallback-type-heads) + (1 font-lock-keyword-face) (2 font-lock-type-face)) + (,(flan-fln--fallback-re flan-fln--fallback-variable-heads) + (1 font-lock-keyword-face) (2 font-lock-variable-name-face)) + (,(flan-fln--fallback-re + (seq-remove (lambda (h) (or (member h flan-fln--fallback-type-heads) + (member h flan-fln--fallback-variable-heads))) + flan--definers)) + (1 font-lock-keyword-face) (2 font-lock-function-name-face)) ;; A condition's parent, `struct DiskFull :parent IoError'. (,(concat "^struct[ \t]+[^][ \t\n(){},;\":]+[ \t]+:parent[ \t]+" flan-fln--name-re) 1 font-lock-type-face) @@ -1602,9 +1636,12 @@ lambda or a `Fn(...)' type, and not after a match arm's." (defvar flan-fln-imenu-generic-expression `(("Functions" ,(concat "^fn-?[ \t]+" flan-fln--name-re) 1) + ("Functions" ,(flan-fln--fallback-re flan-fln--fallback-function-heads) 2) ("Macros" ,(concat "^\\(?:macro[ \t]+\\|defmacro(\\)" flan-fln--name-re) 1) ("Types" ,(concat "^\\(?:struct\\|data\\|union\\|enum\\|type\\)[ \t]+" flan-fln--name-re) 1) - ("Variables" ,(concat "^\\(?:def\\|once\\|const\\)[ \t]+" flan-fln--name-re) 1)) + ("Types" ,(flan-fln--fallback-re flan-fln--fallback-type-heads) 2) + ("Variables" ,(concat "^\\(?:def\\|once\\|const\\)[ \t]+" flan-fln--name-re) 1) + ("Variables" ,(flan-fln--fallback-re flan-fln--fallback-variable-heads) 2)) "Imenu index for `flan-fln-mode'.") (defun flan-fln-current-defun-name () diff --git a/emacs/test-flan-fln.el b/emacs/test-flan-fln.el index fcee3294..892f4204 100644 --- a/emacs/test-flan-fln.el +++ b/emacs/test-flan-fln.el @@ -768,6 +768,46 @@ fn gcd(a: i32, b: i32) -> i32 (test-flan--check "imenu lists the macro" (assoc "repeat" (cdr (assoc "Macros" (imenu--generic-function imenu-generic-expression))))))) +(test-flan-fln--in "defmacro(m, [x]): + quote + ~x + +defmethod(area, point, [p]): + 0 + +defclass(Shape, [w dyn]): + +defstruct(Io, :parent, Error, []) + +defconst(k, 3) +" + (font-lock-ensure) + (let ((face (lambda (needle) + (save-excursion (goto-char (point-min)) (search-forward needle) + (get-text-property (match-beginning 0) 'face))))) + (dolist (c '(("defmacro" m font-lock-function-name-face) + ("defmethod" area font-lock-function-name-face) + ("defclass" Shape font-lock-type-face) + ("defstruct" Io font-lock-type-face) + ("defconst" k font-lock-variable-name-face))) + (test-flan-fln--is (format "a fallback %s's head is a keyword" (car c)) + (funcall face (concat (car c) "(")) 'font-lock-keyword-face) + (test-flan-fln--is (format "and the name it defines, %s" (cadr c)) + (save-excursion + (goto-char (point-min)) + (search-forward (concat (car c) "(")) + (get-text-property (point) 'face)) + (nth 2 c)))) + (let ((index (imenu--generic-function flan-fln-imenu-generic-expression))) + (test-flan--check "imenu lists a fallback defmacro" + (assoc "m" (cdr (assoc "Macros" index)))) + (test-flan--check "a fallback defmethod" + (assoc "area" (cdr (assoc "Functions" index)))) + (test-flan--check "a fallback defclass and defstruct" + (and (assoc "Shape" (cdr (assoc "Types" index))) + (assoc "Io" (cdr (assoc "Types" index))))) + (test-flan--check "and a fallback defconst" + (assoc "k" (cdr (assoc "Variables" index)))))) (test-flan-fln--in "fn far(a: i64,\n b: i64) -> Point\n match a\n Some(x) -> Other\n" (font-lock-ensure) (let ((face (lambda (needle)