From 269887bac2c6646b5bbf71edbf2672adf20943c5 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Fri, 25 Mar 2022 19:51:40 -0700 Subject: [PATCH 01/15] Fix normal param font locking in sigs with type params. Now at least type param lists don't break the font locking of later param lists in the signature. --- go-mode.el | 31 +++++++++++++++---------------- test/go-font-lock-test.el | 4 ++++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/go-mode.el b/go-mode.el index f31e4bff..4ef6185d 100644 --- a/go-mode.el +++ b/go-mode.el @@ -1350,24 +1350,23 @@ declarations are also included." (let (found-match) (while (and (not found-match) - (re-search-forward (concat "\\(\\_<" go-identifier-regexp "\\)?(") end t)) + (search-forward "(" end t)) (when (not (go-in-string-or-comment-p)) (save-excursion - (goto-char (match-beginning 0)) - - (let ((name (match-string 1))) - (when name - ;; We are in a param list if "func" preceded the "(" (i.e. - ;; func literal), or if we are in an interface - ;; declaration, e.g. "interface { foo(i int) }". - (setq found-match (or (string= name "func") (go--in-interface-p)))) - - ;; Otherwise we are in a param list if our "(" is preceded - ;; by ") " or "func ". - (when (and (not found-match) (not (zerop (skip-syntax-backward " ")))) - (setq found-match (or - (eq (char-before) ?\)) - (looking-back "\\_ Date: Thu, 4 Aug 2022 17:02:04 -0700 Subject: [PATCH 02/15] Fontify type params in type and func decls This involves a new anchored matcher to iterate over each param in the list. --- go-mode.el | 93 ++++++++++++++++++++++++++++++++++++--- test/go-font-lock-test.el | 17 +++++-- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/go-mode.el b/go-mode.el index 4ef6185d..70df0340 100644 --- a/go-mode.el +++ b/go-mode.el @@ -441,6 +441,16 @@ statements." ;; flag to allow optional regex groups. (2 font-lock-type-face nil t))) + ;; Anchored matcher for generic type param lists. This handles + ;; type param lists for type/func declarations, and type lists + ;; nested within other type lists. + (go--match-type-list-start + (go--match-type-list-item + nil + (go--match-type-list-post) + (1 font-lock-variable-name-face nil t) + (2 font-lock-type-face nil t))) + ;; Special case to match non-parenthesized function results. For ;; example, "func(i int) string". (go--match-single-func-result 1 font-lock-type-face) @@ -1285,12 +1295,12 @@ INDENT is the normal indent of this line, i.e. that of the case body." (forward-char)))) -(defvar go--fontify-param-has-name nil +(defvar-local go--fontify-param-has-name nil "Whether the current params list has names. This is used during fontification of function signatures.") -(defvar go--fontify-param-beg nil +(defvar-local go--fontify-param-beg nil "Position of \"(\" starting param list. This is used during fontification of function signatures.") @@ -1415,16 +1425,17 @@ the next comma or to the closing paren." (setq done (not (go--search-next-comma end)))) found-match)) -(defun go--search-next-comma (end) +(defun go--search-next-comma (end &optional closer) "Search forward from point for a comma whose nesting level is the same as point. If it reaches a closing parenthesis before a comma, it stops at it. Return non-nil if comma was found." + (setq closer (or closer ?\))) (let ((orig-level (go-paren-level))) (while (and (< (point) end) - (or (looking-at-p "[^,)]") + (or (not (member (char-after) `(?, ,closer))) (> (go-paren-level) orig-level))) (forward-char)) - (when (and (looking-at-p ",") + (when (and (eq (char-after) ?,) (< (point) (1- end))) (forward-char) t))) @@ -3041,7 +3052,79 @@ This handles multi-line comments with a * prefix on each line." (go--with-comment-fill-prefix (lambda () (comment-indent-new-line arg)))) +(defvar-local go--type-list-has-names nil) +(defvar-local go--type-list-start nil) +(defvar-local go--type-list-end nil) + +(defun go--match-type-list-start (end) + "Search for [ starting a type list" + (let (found-match) + (while (and + (not found-match) + (search-forward "[" end t)) + (when (not (go-in-string-or-comment-p)) + (if (and + go--type-list-start + (< go--type-list-start (point)) + (> go--type-list-end (point))) + ;; A "[" within an outer type list is always a type list. + (progn + (setq found-match t) + (setq go--type-list-has-names nil)) + + (setq go--type-list-start nil + go--type-list-end nil + go--type-list-has-names nil) + + (when (save-excursion + (backward-char) + (or + ;; Directly in a "type" or "func" type list + (go--looking-back-p (concat "^[[:space:]]*\\(?:func\\|type\\)[[:space:]]+" go-identifier-regexp "$")) + (and + ;; Or on LHS of item in type decl + (equal (go--containing-decl) "type") + (go--looking-back-p (concat "^[[:space:]]*" go-identifier-regexp "$"))))) + (setq found-match t) + (setq go--type-list-start (point)) + (setq go--type-list-has-names t))))) + found-match)) +(defun go--match-type-list-post () + "Jump back to start of list so we can find nested lists" + (setq go--type-list-end (point)) + (goto-char go--type-list-start)) + +(defconst go--type-list-item-with-name-re + (concat "\\(" go-identifier-regexp "\\)\\(?:[[:space:]]+" go-type-name-regexp "\\)?")) + +(defconst go--type-list-item-without-name-re + ;; Add a dummy capture group so things are balanced with above re. + (concat "\\(\\)" go-type-name-regexp)) + +(defun go--match-type-list-item (end) + "Advance through each type param in a type instantion param list." + (let (found-match done name type) + (while (and (not found-match) (not done)) + (skip-syntax-forward " ") + + (if go--type-list-has-names + (setq found-match (looking-at go--type-list-item-with-name-re)) + (setq found-match (looking-at go--type-list-item-without-name-re))) + + (when found-match + (let ((md (match-data))) + (when (not go-fontify-variables) + ;; Zero out match data to disable variable fontification. + (setf (nth 2 md) nil (nth 3 md) nil)) + (when (member (match-string 2) go-mode-keywords) + ;; Zero out second match if it was a keyword (e.g. "[A interface { ... }]" + (setf (nth 4 md) nil (nth 5 md) nil)) + (set-match-data md))) + + (setq done (not (go--search-next-comma end ?\])))) + + found-match)) (provide 'go-mode) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 36dd4bb1..c3d034db 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -46,8 +46,17 @@ QKfuncK (VfV TintT) {} ")) (ert-deftest go--fontify-generic-signature () - (go--should-fontify "KfuncK FfooF[a TintT](VaV TintT) { }") - (go--should-fontify "KfuncK FfooF[a TintT](TintT) { }")) + (go--should-fontify "KfuncK FfooF[VaV TintT](VaV TintT) { }") + (go--should-fontify "KfuncK FfooF[VaV TintT](TintT) { }")) + +(ert-deftest go--fontify-generic-type-decl () + (go--should-fontify "KtypeK TfooT[VaV, VbV TcT[TdT]] TbarT[e]") + (go--should-fontify "KtypeK ( + TfooT[VaV TbT] TbarT[c] +)") + (go--should-fontify "KtypeK ( + TfooT = TbarT[c] +)")) (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") @@ -257,7 +266,9 @@ expects \"make\" to be a (B)uiltin and \"int\" to be a (T)type." ;; First pass through buffer looks for the face tags. We delete ;; the tags and record the expected face ranges in `faces'. - (let ((case-fold-search nil) faces start start-pos) + (let ((case-fold-search nil) + (go-fontify-variables t) + faces start start-pos) (while (re-search-forward "[TBKCFSNVDQ]" nil t) (let ((found-char (char-before))) (backward-delete-char 1) From 9f58789c99e1506ad55eb05b140d8391150cf6e2 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Thu, 4 Aug 2022 20:04:36 -0700 Subject: [PATCH 03/15] Fontify type args in new()/make() This uses a cheeky approach where we piggyback off existing type matching. After using the existing matcher to fontify "foo" in make(foo[bar]), we then check if the next char is "[", and if so we know it is a type list, so we fontify it as such. This approach will be used in later commits for all the other simple type matchers. --- go-mode.el | 28 ++++++++++++++++++++++++++-- test/go-font-lock-test.el | 8 +++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/go-mode.el b/go-mode.el index 70df0340..7175dc46 100644 --- a/go-mode.el +++ b/go-mode.el @@ -513,7 +513,7 @@ statements." (,(concat "\\_[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; "new()"/"make()" type - (,(concat "\\_<\\(?:new\\|make\\)\\_>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) + (eval . (go--make-type-matcher (concat "\\_<\\(?:new\\|make\\)\\_>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp))) ;; Type assertion (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) @@ -3052,6 +3052,17 @@ This handles multi-line comments with a * prefix on each line." (go--with-comment-fill-prefix (lambda () (comment-indent-new-line arg)))) +(defun go--make-type-matcher (regex-or-func &optional match-idx) + "Wrap a simple type matcher so we automatically match an abutting +type param list, if present." + (setq match-idx (or match-idx 1)) + `(,regex-or-func + (,match-idx font-lock-type-face) + (go--match-type-list-item + (go--match-type-list-piggyback-pre ,match-idx) + nil + (2 font-lock-type-face nil t)))) + (defvar-local go--type-list-has-names nil) (defvar-local go--type-list-start nil) (defvar-local go--type-list-end nil) @@ -3090,6 +3101,19 @@ This handles multi-line comments with a * prefix on each line." (setq go--type-list-has-names t))))) found-match)) +(defun go--match-type-list-piggyback-pre (match-idx) + ;; match-idx corresponds to a type, so if the next char is "[", we + ;; know it is a type list. + (if (and + (not (member (match-string match-idx) go-mode-keywords)) + (eq (char-after (match-end match-idx)) ?\[)) + (progn + (goto-char (match-end match-idx)) + (forward-char) + (setq go--type-list-has-names nil) + (setq go--type-list-start (point))) + (setq go--type-list-start nil))) + (defun go--match-type-list-post () "Jump back to start of list so we can find nested lists" (setq go--type-list-end (point)) @@ -3105,7 +3129,7 @@ This handles multi-line comments with a * prefix on each line." (defun go--match-type-list-item (end) "Advance through each type param in a type instantion param list." (let (found-match done name type) - (while (and (not found-match) (not done)) + (while (and go--type-list-start (not found-match) (not done)) (skip-syntax-forward " ") (if go--type-list-has-names diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index c3d034db..77b6e29f 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -58,6 +58,12 @@ QKfuncK (VfV TintT) {} TfooT = TbarT[c] )")) +(ert-deftest go--fontify-make-new () + (go--should-fontify "BmakeB(TfooT)") + (go--should-fontify "BmakeB(TfooT[TbarT])") + (go--should-fontify "BnewB(TfooT)") + (go--should-fontify "BnewB(TfooT[TbarT])")) + (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") (go--should-fontify "KstructK { a, b TintT }") @@ -257,7 +263,7 @@ represent expected font lock face names. For example: BmakeB([]TintT, 0) -expects \"make\" to be a (B)uiltin and \"int\" to be a (T)type." +expects \"make\" to be a (B)uiltin and \"int\" to be a (T)ype." (with-temp-buffer (setq mode (or mode 'go-mode)) (funcall mode) From 7e14db1514fc4c09f93fc80697511889333fb672 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Thu, 4 Aug 2022 20:44:19 -0700 Subject: [PATCH 04/15] Fontify nest type args When fontifying a type list if we see another "[" we presume it is a nested type list and fontify it accordingly. --- go-mode.el | 26 ++++++++++++++++++++++++-- test/go-font-lock-test.el | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/go-mode.el b/go-mode.el index 7175dc46..175e0fd9 100644 --- a/go-mode.el +++ b/go-mode.el @@ -3066,6 +3066,7 @@ type param list, if present." (defvar-local go--type-list-has-names nil) (defvar-local go--type-list-start nil) (defvar-local go--type-list-end nil) +(defvar-local go--type-list-paren-level nil) (defun go--match-type-list-start (end) "Search for [ starting a type list" @@ -3076,6 +3077,7 @@ type param list, if present." (when (not (go-in-string-or-comment-p)) (if (and go--type-list-start + go--type-list-end (< go--type-list-start (point)) (> go--type-list-end (point))) ;; A "[" within an outer type list is always a type list. @@ -3111,7 +3113,8 @@ type param list, if present." (goto-char (match-end match-idx)) (forward-char) (setq go--type-list-has-names nil) - (setq go--type-list-start (point))) + (setq go--type-list-start (point)) + (setq go--type-list-paren-level (1+ (go-paren-level)))) (setq go--type-list-start nil))) (defun go--match-type-list-post () @@ -3129,6 +3132,10 @@ type param list, if present." (defun go--match-type-list-item (end) "Advance through each type param in a type instantion param list." (let (found-match done name type) + ;; Make sure we are at the start of a list item. + (when (not (member (char-before) '(?, ?\[))) + (setq done t)) + (while (and go--type-list-start (not found-match) (not done)) (skip-syntax-forward " ") @@ -3146,7 +3153,22 @@ type param list, if present." (setf (nth 4 md) nil (nth 5 md) nil)) (set-match-data md))) - (setq done (not (go--search-next-comma end ?\])))) + (if go--type-list-paren-level + (if (and + found-match + (not go--type-list-has-names) + (goto-char (match-end 2)) + (eq (char-after) ?\[)) + ;; If there is "[" after match, recurse into the nested + ;; param list. + (forward-char) + (when (not (go--search-next-comma end ?\])) + (while (eq (char-after) ?\]) + (forward-char)) + ;; We made it to the end of a "[]" list. Check if it was our + ;; starting paren depth. + (setq done (<= (go-paren-level) go--type-list-paren-level)))) + (setq done (not (go--search-next-comma end ?\]))))) found-match)) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 77b6e29f..44597f87 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -61,6 +61,7 @@ QKfuncK (VfV TintT) {} (ert-deftest go--fontify-make-new () (go--should-fontify "BmakeB(TfooT)") (go--should-fontify "BmakeB(TfooT[TbarT])") + (go--should-fontify "BmakeB(TfooT[TbarT[TbazT]])") (go--should-fontify "BnewB(TfooT)") (go--should-fontify "BnewB(TfooT[TbarT])")) From 7068d8e956e9a39d80f9049afe5f44ea0a462c0b Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Fri, 5 Aug 2022 08:37:32 -0700 Subject: [PATCH 05/15] Fontify type params in func signatures --- go-mode.el | 4 ++-- test/go-font-lock-test.el | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go-mode.el b/go-mode.el index 175e0fd9..5c468c36 100644 --- a/go-mode.el +++ b/go-mode.el @@ -1387,7 +1387,7 @@ declarations are also included." func(i int, s *string) { }") (defconst go--unnamed-param-re - (concat "\\(\\)[[:space:]\n]*\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)]") + (concat "\\(\\)[[:space:]\n]*\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)[]") "Regexp to match unnamed param such as \"*string\" in: func(int, *string) { } @@ -1666,7 +1666,7 @@ after '('." (goto-char (match-end 0)) (go--parameter-list-type end)) ((or (looking-at go-qualified-identifier-regexp) - (looking-at (concat go-type-name-no-prefix-regexp "[[:space:]\n]*\\(?:)\\|\\'\\)")) + (looking-at (concat go-type-name-no-prefix-regexp "[[:space:]\n]*\\(?:)\\|\\[\\|\\'\\)")) (go--looking-at-keyword) (looking-at "[*\\[]\\|\\.\\.\\.\\|\\'")) 'absent) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 44597f87..72923f7f 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -56,7 +56,10 @@ QKfuncK (VfV TintT) {} )") (go--should-fontify "KtypeK ( TfooT = TbarT[c] -)")) +)") + + (go--should-fontify "KtypeK TfooT[VaV KfuncK(TbT[TcT])]") + (go--should-fontify "KtypeK TfooT[VaV KfuncK(VaV TbT[TcT])]")) (ert-deftest go--fontify-make-new () (go--should-fontify "BmakeB(TfooT)") From 0e54930b263b9b69ca8c0aca08c0586164ce3750 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Fri, 25 Mar 2022 21:44:46 -0700 Subject: [PATCH 06/15] Fontify type unions --- go-mode.el | 45 +++++++++++++++++++++++++++++++++++++++ test/go-font-lock-test.el | 5 +++++ 2 files changed, 50 insertions(+) diff --git a/go-mode.el b/go-mode.el index 5c468c36..7ea73f6d 100644 --- a/go-mode.el +++ b/go-mode.el @@ -458,6 +458,9 @@ statements." ;; Match name+type pairs, such as "foo bar" in "var foo bar". (go--match-ident-type-pair 2 font-lock-type-face) + ;; Match type unions such as "int | string" in "interface { int | string }". + (eval . (go--make-type-matcher 'go--match-type-union)) + ;; An anchored matcher for type switch case clauses. (go--match-type-switch-case (go--fontify-type-switch-case @@ -743,6 +746,16 @@ case keyword. It returns nil for the case line itself." "Return non-nil if point is inside a type switch statement." (go--in-paren-with-prefix-p ?{ ".(type)")) +(defun go--in-type-params-p () + "Return non-nil if point is inside a type param list." + (save-excursion + (and + (go-goto-opening-parenthesis) + (eq (char-after) ?\[) + (backward-word) + (skip-syntax-backward " ") + (member (thing-at-point 'word 'no-properties) '("type" "func"))))) + (defun go--open-paren-position () "Return non-nil if point is between '(' and ')'. @@ -1579,6 +1592,38 @@ succeeds." found-match)) +(defconst go--type-union-re + (concat go-type-name-regexp "\\(?:\\[.*?\\]\\)?\\s-*|\\||\\s-*" go-type-name-regexp)) + +(defun go--match-type-union (end) + "Search for type unions in interfaces and constraints." + (let (found-match) + (while (and + (not found-match) + ;; Look for "foo |" or "| foo" (i.e. a type name before or + ;; after a pipe). + (re-search-forward go--type-union-re end t)) + + (if (match-string 2) + ;; If second group matched, move it into the first group to + ;; keep other logic simpler. + (let ((md (match-data))) + (setf (nth 2 md) (nth 4 md) (nth 3 md) (nth 5 md)) + (set-match-data md))) + + (let ((level (go-paren-level))) + (goto-char (match-end 1)) + ;; We have a dumb "\\[.*?\\]" in the regex which can match + ;; more than we want, so make sure our paren level is the same + ;; before and after the matched type name. + (when (= level (go-paren-level)) + (setq found-match (and + (not (member (match-string 1) go-mode-keywords)) + (or + (go--in-interface-p) + (go--in-type-params-p))))))) + found-match)) + (defconst go--single-func-result-re (concat ")[[:space:]]+" go-type-name-regexp "\\(?:$\\|[[:space:]),]\\)")) (defun go--match-single-func-result (end) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 72923f7f..b80aa837 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -68,6 +68,11 @@ QKfuncK (VfV TintT) {} (go--should-fontify "BnewB(TfooT)") (go--should-fontify "BnewB(TfooT[TbarT])")) +(ert-deftest go--fontify-type-union () + (go--should-fontify "KfuncK FfooF[VaV TintT | TstringT | KstructK{} | *Tfoo.ZebraT](TintT) { }") + (go--should-fontify "KinterfaceK { TintT | Tfloat64T }") + (go--should-fontify "KfuncK FfooF[VaV TfooT[TbarT[TbazT]] | TfooT[TbarT[TbazT]]](TintT) { }")) + (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") (go--should-fontify "KstructK { a, b TintT }") From 67da7ab2625fadb16b2e991f58a33e53d6228055 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Sat, 26 Mar 2022 22:05:40 -0700 Subject: [PATCH 07/15] Fix fontification of generic func names. Now we fontify func names in generic invocations when there are more than one type params (so we can be sure it is a func instantiation). I also tweaked things to avoid fontifying "bar" in (foo)(bar)(baz) (fixes #385). We also support fontifying method names with type params even though that isn't allowed yet. --- go-mode.el | 42 +++++++++++++++++++++++++++++++++------ test/go-font-lock-test.el | 10 ++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/go-mode.el b/go-mode.el index 7ea73f6d..22ad8802 100644 --- a/go-mode.el +++ b/go-mode.el @@ -484,9 +484,9 @@ statements." (if go-fontify-function-calls ;; Function call/method name - `((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-function-name-face) - ;; Bracketed function call - (,(concat "[^[:word:][:multibyte:]](\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock-function-name-face)) + `((go--match-func-name + (1 font-lock-function-name-face nil t) + (2 font-lock-function-name-face nil t))) ;; Method name `((,go-func-meth-regexp 2 font-lock-function-name-face))) @@ -1435,10 +1435,10 @@ the next comma or to the closing paren." (setq found-match t))) ;; Advance to next comma. We are done if there are no more commas. - (setq done (not (go--search-next-comma end)))) + (setq done (not (go--search-next-comma end ?\))))) found-match)) -(defun go--search-next-comma (end &optional closer) +(defun go--search-next-comma (end closer) "Search forward from point for a comma whose nesting level is the same as point. If it reaches a closing parenthesis before a comma, it stops at it. Return non-nil if comma was found." @@ -1480,7 +1480,7 @@ comma, it stops at it. Return non-nil if comma was found." (goto-char (match-end 1)) (unless (member (match-string 1) go-constants) (setq found-match t))) - (setq done (not (go--search-next-comma end)))) + (setq done (not (go--search-next-comma end ?\))))) found-match)) (defun go--containing-decl () @@ -1661,6 +1661,36 @@ We are looking for the right-hand-side of the type alias" found-match)) +(defconst go--match-func-name-re + (concat "\\(?:^\\|[^)]\\)(\\(" go-identifier-regexp "\\))(\\|\\(" go-identifier-regexp "\\)[[(]")) + +(defun go--match-func-name (end) + "Search for func names in decls and invocations." + (let (found-match) + (while (and + (not found-match) + ;; Match "(foo)(" or "foo[" or "foo(". + (re-search-forward go--match-func-name-re end t)) + (if (eq (char-before) ?\() + ;; Followed directly by "(" is a match. + (setq found-match t) + ;; Followed by "[". We are a match if there is at least one + ;; comma between the "[" and "]", and if "]" is followed by + ;; "(". + (let ((commas 0)) + (save-excursion + (while (go--search-next-comma end ?\]) + (cl-incf commas)) + (forward-char) + (setq found-match (and + ;; We found a comma (so we are sure these are type + ;; params), or we are at file level (so we are sure + ;; it is a func decl). + (or (> commas 0) (= 0 (go-paren-level))) + (eq (char-after) ?\())))))) + found-match)) + + (defconst go--map-value-re (concat "\\_\\[\\(?:\\[[^]]*\\]\\)*[^]]*\\]" go-type-name-regexp)) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index b80aa837..19f9cd69 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -73,6 +73,16 @@ QKfuncK (VfV TintT) {} (go--should-fontify "KinterfaceK { TintT | Tfloat64T }") (go--should-fontify "KfuncK FfooF[VaV TfooT[TbarT[TbazT]] | TfooT[TbarT[TbazT]]](TintT) { }")) +(ert-deftest go--fontify-func () + (go--should-fontify "KfuncK FfooF()") + (go--should-fontify "KfuncK FfooF[VAV TanyT]()") + (go--should-fontify "KfuncK (VfV TfooT) FfooF[A TanyT]()") + (go--should-fontify "FfooF(bar)") + (go--should-fontify "foo.FfooF(bar)") + (go--should-fontify "(FfooF)(foo)(foo)") + (go--should-fontify "{ foo[int](123) }") + (go--should-fontify "FfooF[int, string](123)")) + (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") (go--should-fontify "KstructK { a, b TintT }") From f989ce2844dd09eedc68186466595dc21d3b6784 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 3 Aug 2022 10:15:56 -0700 Subject: [PATCH 08/15] Fontify type params in type assertions --- go-mode.el | 2 +- test/go-font-lock-test.el | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go-mode.el b/go-mode.el index 22ad8802..10bb4914 100644 --- a/go-mode.el +++ b/go-mode.el @@ -519,7 +519,7 @@ statements." (eval . (go--make-type-matcher (concat "\\_<\\(?:new\\|make\\)\\_>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp))) ;; Type assertion - (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) + (eval . (go--make-type-matcher (concat "\\.\\s *(" go-type-name-regexp))) ;; Composite literal field names and label definitions. (go--match-ident-colon 1 font-lock-constant-face) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 19f9cd69..394472cb 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -83,6 +83,10 @@ QKfuncK (VfV TintT) {} (go--should-fontify "{ foo[int](123) }") (go--should-fontify "FfooF[int, string](123)")) +(ert-deftest go--fontify-type-instantiation () + (go--should-fontify "BnewB(TfooT[TbarT])") + (go--should-fontify "foo.(*TbarT[TbazT])")) + (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") (go--should-fontify "KstructK { a, b TintT }") From d02691c1fa8d1edbfbc16baf29e1cd458bbdc10b Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 3 Aug 2022 10:17:31 -0700 Subject: [PATCH 09/15] Fontify type params in chan types. --- go-mode.el | 2 +- test/go-font-lock-test.el | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go-mode.el b/go-mode.el index 10bb4914..90c33f33 100644 --- a/go-mode.el +++ b/go-mode.el @@ -513,7 +513,7 @@ statements." (,(concat "\\_\\[" go-type-name-regexp) 1 font-lock-type-face) ;; Channel type - (,(concat "\\_[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 font-lock-type-face) + (eval . (go--make-type-matcher (concat "\\_[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp))) ;; "new()"/"make()" type (eval . (go--make-type-matcher (concat "\\_<\\(?:new\\|make\\)\\_>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp))) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 394472cb..9c08915c 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -85,7 +85,8 @@ QKfuncK (VfV TintT) {} (ert-deftest go--fontify-type-instantiation () (go--should-fontify "BnewB(TfooT[TbarT])") - (go--should-fontify "foo.(*TbarT[TbazT])")) + (go--should-fontify "foo.(*TbarT[TbazT])") + (go--should-fontify "KchanK TfooT[TbarT]")) (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") From 786b59b3da1965a71f5b1089ddf60f94c8c37f2a Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 3 Aug 2022 14:25:26 -0700 Subject: [PATCH 10/15] Fontify type params in "map" types For example, we now fontify a, b, c and d as types: map[a[b]]c[d] This required tweaking the map value matcher to allow square brackets to appear in the map key type. --- go-mode.el | 29 +++++++++++++++++++---------- test/go-font-lock-test.el | 4 +++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/go-mode.el b/go-mode.el index 90c33f33..18ffdde9 100644 --- a/go-mode.el +++ b/go-mode.el @@ -507,10 +507,10 @@ statements." (,(concat go-type-name-regexp "{") 1 font-lock-type-face) ;; Map value type - (go--match-map-value 1 font-lock-type-face) + (eval . (go--make-type-matcher 'go--match-map-value)) ;; Map key type - (,(concat "\\_\\[" go-type-name-regexp) 1 font-lock-type-face) + (eval . (go--make-type-matcher (concat "\\_\\[" go-type-name-regexp))) ;; Channel type (eval . (go--make-type-matcher (concat "\\_[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp))) @@ -1690,17 +1690,25 @@ We are looking for the right-hand-side of the type alias" (eq (char-after) ?\())))))) found-match)) - -(defconst go--map-value-re - (concat "\\_\\[\\(?:\\[[^]]*\\]\\)*[^]]*\\]" go-type-name-regexp)) +(defconst go--map-value-re (concat "\\]" go-type-name-regexp)) (defun go--match-map-value (end) "Search for map value types." - (when (re-search-forward go--map-value-re end t) - ;; Move point to beginning of map value in case value itself is - ;; also a map (we will match it next iteration). - (goto-char (match-beginning 1)) - t)) + (let (found-match) + ;; search for "]someType", then bounce to opening "[" and check if + ;; preceded by "map". + (while (and + (not found-match) + (search-forward-regexp go--map-value-re end t)) + (goto-char (match-beginning 0)) + (forward-char) + (save-excursion + (save-match-data + (backward-char) + (go-goto-opening-parenthesis) + (backward-word) + (setq found-match (looking-at-p "map"))))) + found-match)) (defconst go--label-re (concat "\\(" go-label-regexp "\\):")) @@ -3232,6 +3240,7 @@ type param list, if present." (if (and found-match (not go--type-list-has-names) + (match-string 2) (goto-char (match-end 2)) (eq (char-after) ?\[)) ;; If there is "[" after match, recurse into the nested diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 9c08915c..b94991f6 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -86,7 +86,9 @@ QKfuncK (VfV TintT) {} (ert-deftest go--fontify-type-instantiation () (go--should-fontify "BnewB(TfooT[TbarT])") (go--should-fontify "foo.(*TbarT[TbazT])") - (go--should-fontify "KchanK TfooT[TbarT]")) + (go--should-fontify "KchanK TfooT[TbarT]") + (go--should-fontify "KmapK[TaT[TbT]]TcT[TdT]") + (go--should-fontify "KmapK[TaT[TbT]]KmapK[TcT[TdT]]TeT[TfT]")) (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") From 3f6dfa53246a66d5cfdffd6a7b9c988d3f6b0358 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 3 Aug 2022 15:14:31 -0700 Subject: [PATCH 11/15] Fix fontification of composite literals w/ type params. Fix "foo.Bar", "int" and "string" are now fontified as types in "foo.Bar[int, string]{}". --- go-mode.el | 24 +++++++++++++++++++++++- test/go-font-lock-test.el | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/go-mode.el b/go-mode.el index 18ffdde9..a5847ce9 100644 --- a/go-mode.el +++ b/go-mode.el @@ -504,7 +504,7 @@ statements." ("\\(!\\)[^=]" 1 font-lock-negation-char-face) ;; Composite literal type - (,(concat go-type-name-regexp "{") 1 font-lock-type-face) + (eval . (go--make-type-matcher 'go--match-composite-literal)) ;; Map value type (eval . (go--make-type-matcher 'go--match-map-value)) @@ -1710,6 +1710,28 @@ We are looking for the right-hand-side of the type alias" (setq found-match (looking-at-p "map"))))) found-match)) +(defconst go--match-composite-literal-re (concat go-type-name-regexp "[[{]")) + +(defun go--match-composite-literal (end) + "Search for composite literals." + (let (found-match) + (while (and + (not found-match) + ;; Match "foo{" or "foo[". + (re-search-forward go--match-composite-literal-re end t)) + + (setq found-match + (if (eq (char-before) ?\[) + ;; In "foo[" case, skip to closing "]". + (progn + (while (go--search-next-comma end ?\])) + ;; See if closing "]" is followed by "{". + (eq (char-after (1+ (point))) ?{)) + ;; The "foo{" case (definitely composite literal). + (eq (char-before) ?{)))) + + found-match)) + (defconst go--label-re (concat "\\(" go-label-regexp "\\):")) (defun go--match-ident-colon (end) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index b94991f6..f0f9e7e1 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -143,6 +143,7 @@ KcaseK string: (go--should-fontify "TfooT{") (go--should-fontify "[]TfooT{") (go--should-fontify "Tfoo.ZarT{") + (go--should-fontify "Tfoo.ZarT[TintT]{") (go--should-fontify "[]Tfoo.ZarT{") (go--should-fontify "TfooT{CbarC:baz, CquxC: 123}") From dd4316001728e68f08aaddcbe0ff01462f288bbc Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 3 Aug 2022 15:53:57 -0700 Subject: [PATCH 12/15] Fontify type args in slice/array types We now fontify "foo" and "bar" as types in: []foo[bar] --- go-mode.el | 2 +- test/go-font-lock-test.el | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go-mode.el b/go-mode.el index a5847ce9..e7b82be6 100644 --- a/go-mode.el +++ b/go-mode.el @@ -498,7 +498,7 @@ statements." (go--match-type-alias 2 font-lock-type-face) ;; Arrays/slices: [] | [123] | [some.Const] | [someConst] | [...] - (,(concat "\\(?:^\\|[^[:word:][:multibyte:]]\\)\\[\\(?:[[:digit:]]+\\|" go-qualified-identifier-regexp "\\|" go-identifier-regexp "\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 1 font-lock-type-face) + (eval . (go--make-type-matcher (concat "\\(?:^\\|[^[:word:][:multibyte:]]\\)\\[\\(?:[[:digit:]]+\\|" go-qualified-identifier-regexp "\\|" go-identifier-regexp "\\|\\.\\.\\.\\)?\\]" go-type-name-regexp))) ;; Unary "!" ("\\(!\\)[^=]" 1 font-lock-negation-char-face) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index f0f9e7e1..ea36de23 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -88,7 +88,8 @@ QKfuncK (VfV TintT) {} (go--should-fontify "foo.(*TbarT[TbazT])") (go--should-fontify "KchanK TfooT[TbarT]") (go--should-fontify "KmapK[TaT[TbT]]TcT[TdT]") - (go--should-fontify "KmapK[TaT[TbT]]KmapK[TcT[TdT]]TeT[TfT]")) + (go--should-fontify "KmapK[TaT[TbT]]KmapK[TcT[TdT]]TeT[TfT]") + (go--should-fontify "[]TfooT[TbarT[TbazT]]")) (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") From 679945f3a1ca9321600a24bec14304b88672ace4 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 3 Aug 2022 15:59:45 -0700 Subject: [PATCH 13/15] Fontify type args in RHS of type alias We now fontify "baz" as a type: type foo = bar[baz] --- go-mode.el | 2 +- test/go-font-lock-test.el | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go-mode.el b/go-mode.el index e7b82be6..ec0bb1ae 100644 --- a/go-mode.el +++ b/go-mode.el @@ -495,7 +495,7 @@ statements." ("\\(`[^`]*`\\)" 1 font-lock-multiline) ;; RHS of type alias. - (go--match-type-alias 2 font-lock-type-face) + (eval . (go--make-type-matcher 'go--match-type-alias 2)) ;; Arrays/slices: [] | [123] | [some.Const] | [someConst] | [...] (eval . (go--make-type-matcher (concat "\\(?:^\\|[^[:word:][:multibyte:]]\\)\\[\\(?:[[:digit:]]+\\|" go-qualified-identifier-regexp "\\|" go-identifier-regexp "\\|\\.\\.\\.\\)?\\]" go-type-name-regexp))) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index ea36de23..53721dd6 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -55,7 +55,7 @@ QKfuncK (VfV TintT) {} TfooT[VaV TbT] TbarT[c] )") (go--should-fontify "KtypeK ( - TfooT = TbarT[c] + TfooT = TbarT[TcT] )") (go--should-fontify "KtypeK TfooT[VaV KfuncK(TbT[TcT])]") @@ -89,7 +89,8 @@ QKfuncK (VfV TintT) {} (go--should-fontify "KchanK TfooT[TbarT]") (go--should-fontify "KmapK[TaT[TbT]]TcT[TdT]") (go--should-fontify "KmapK[TaT[TbT]]KmapK[TcT[TdT]]TeT[TfT]") - (go--should-fontify "[]TfooT[TbarT[TbazT]]")) + (go--should-fontify "[]TfooT[TbarT[TbazT]]") + (go--should-fontify "KtypeK TfooT = TbarT[TbazT]")) (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") From d139025b307b1b45fd125ed8f2286cc958db1350 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 5 Oct 2022 19:57:34 -0700 Subject: [PATCH 14/15] Fontify type args in a couple more spots "bar" is now fontified as a type in: func _() foo[bar] {} var _ foo[bar] --- go-mode.el | 6 +++--- test/go-font-lock-test.el | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go-mode.el b/go-mode.el index ec0bb1ae..18caafb5 100644 --- a/go-mode.el +++ b/go-mode.el @@ -453,10 +453,10 @@ statements." ;; Special case to match non-parenthesized function results. For ;; example, "func(i int) string". - (go--match-single-func-result 1 font-lock-type-face) + (eval . (go--make-type-matcher 'go--match-single-func-result)) ;; Match name+type pairs, such as "foo bar" in "var foo bar". - (go--match-ident-type-pair 2 font-lock-type-face) + (eval . (go--make-type-matcher 'go--match-ident-type-pair 2)) ;; Match type unions such as "int | string" in "interface { int | string }". (eval . (go--make-type-matcher 'go--match-type-union)) @@ -1624,7 +1624,7 @@ succeeds." (go--in-type-params-p))))))) found-match)) -(defconst go--single-func-result-re (concat ")[[:space:]]+" go-type-name-regexp "\\(?:$\\|[[:space:]),]\\)")) +(defconst go--single-func-result-re (concat ")[[:space:]]+" go-type-name-regexp "\\(?:\\[.*\\]\\)?\\(?:$\\|[[:space:]),]\\)")) (defun go--match-single-func-result (end) "Match single result types. diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 53721dd6..14d76c75 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -77,6 +77,8 @@ QKfuncK (VfV TintT) {} (go--should-fontify "KfuncK FfooF()") (go--should-fontify "KfuncK FfooF[VAV TanyT]()") (go--should-fontify "KfuncK (VfV TfooT) FfooF[A TanyT]()") + (go--should-fontify "KfuncK FfooF[VAV TanyT]() TbarT") + (go--should-fontify "KfuncK FfooF[VAV TanyT]() TbarT[TbazT]") (go--should-fontify "FfooF(bar)") (go--should-fontify "foo.FfooF(bar)") (go--should-fontify "(FfooF)(foo)(foo)") @@ -215,6 +217,7 @@ KtypeK ( (go--should-fontify "KvarK VfooV, VbarV = bar, baz") (go--should-fontify "KvarK VfooV TbarT D// DQcoolQ") (go--should-fontify "KvarK VfooV TbarT = baz") + (go--should-fontify "KvarK VfooV TbarT[TbazT] = qux") (go--should-fontify "KvarK VfooV KstructK { i TintT } = baz") (go--should-fontify "KvarK VfooV []*Tfoo.ZarT D// DQcoolQ") From aa7099e6c2d0d11ea73676f6a56e078e095c1855 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Wed, 5 Oct 2022 21:05:28 -0700 Subject: [PATCH 15/15] Fontify any index list as a type list If there is more than one thing in between "[]", we know it is a type list. Now we fontify "int" and "string" as types in: foo[int, string]() --- go-mode.el | 32 +++++++++++++++++++------------- test/go-font-lock-test.el | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/go-mode.el b/go-mode.el index 18caafb5..958ad63a 100644 --- a/go-mode.el +++ b/go-mode.el @@ -3193,19 +3193,25 @@ type param list, if present." (setq go--type-list-start nil go--type-list-end nil go--type-list-has-names nil) - - (when (save-excursion - (backward-char) - (or - ;; Directly in a "type" or "func" type list - (go--looking-back-p (concat "^[[:space:]]*\\(?:func\\|type\\)[[:space:]]+" go-identifier-regexp "$")) - (and - ;; Or on LHS of item in type decl - (equal (go--containing-decl) "type") - (go--looking-back-p (concat "^[[:space:]]*" go-identifier-regexp "$"))))) - (setq found-match t) - (setq go--type-list-start (point)) - (setq go--type-list-has-names t))))) + (if (save-excursion + (backward-char) + (or + ;; Directly in a "type" or "func" type list + (go--looking-back-p (concat "^[[:space:]]*\\(?:func\\|type\\)[[:space:]]+" go-identifier-regexp "$")) + (and + ;; Or on LHS of item in type decl + (equal (go--containing-decl) "type") + (go--looking-back-p (concat "^[[:space:]]*" go-identifier-regexp "$"))))) + (setq found-match t + go--type-list-start (point) + go--type-list-has-names t) + (when (save-excursion (go--search-next-comma end ?\])) + ;; If we aren't in a "func" or "type" decl and there is + ;; more than one item in the list, it must be a type + ;; list. + (setq found-match t + go--type-list-start (point) + go--type-list-has-names nil)))))) found-match)) (defun go--match-type-list-piggyback-pre (match-idx) diff --git a/test/go-font-lock-test.el b/test/go-font-lock-test.el index 14d76c75..dbca8ae8 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -83,7 +83,7 @@ QKfuncK (VfV TintT) {} (go--should-fontify "foo.FfooF(bar)") (go--should-fontify "(FfooF)(foo)(foo)") (go--should-fontify "{ foo[int](123) }") - (go--should-fontify "FfooF[int, string](123)")) + (go--should-fontify "FfooF[TintT, TstringT](123)")) (ert-deftest go--fontify-type-instantiation () (go--should-fontify "BnewB(TfooT[TbarT])")