From 4baab54c416216a6eb5154b778d54ff25c2da8bd Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Fri, 25 Mar 2022 19:51:40 -0700 Subject: [PATCH 1/5] 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 cdebdc3c..9756f7b8 100644 --- a/go-mode.el +++ b/go-mode.el @@ -1375,24 +1375,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: Fri, 25 Mar 2022 21:44:46 -0700 Subject: [PATCH 2/5] Fontify type unions. --- go-mode.el | 40 +++++++++++++++++++++++++++++++++++++++ test/go-font-lock-test.el | 4 ++++ 2 files changed, 44 insertions(+) diff --git a/go-mode.el b/go-mode.el index 9756f7b8..396b9ca3 100644 --- a/go-mode.el +++ b/go-mode.el @@ -448,6 +448,11 @@ 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 }". + (go--match-type-union + (1 font-lock-type-face nil t) + (2 font-lock-type-face nil t)) + ;; An anchored matcher for type switch case clauses. (go--match-type-switch-case (go--fontify-type-switch-case @@ -733,6 +738,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--fill-prefix () "Return fill prefix for following comment paragraph." (save-excursion @@ -1593,6 +1608,31 @@ 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)) + + (let ((match (match-string 1))) + ;; If we matched "foo |", move back one char so we can see the + ;; pipe again on the next iteration. + (if match + (backward-char) + (setq match (match-string 2))) + + (setq found-match (and + (not (member match 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 d2f18e23..47d7ce58 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -49,6 +49,10 @@ QKfuncK (VfV TintT) {} (go--should-fontify "KfuncK FfooF[a TintT](VaV TintT) { }") (go--should-fontify "KfuncK FfooF[a TintT](TintT) { }")) +(ert-deftest go--fontify-type-union () + (go--should-fontify "KfuncK FfooF[a TintT | TstringT | KstructK{} | *Tfoo.ZebraT](TintT) { }") + (go--should-fontify "KinterfaceK { TintT | Tfloat64T }")) + (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") (go--should-fontify "KstructK { a, b TintT }") From 54c02fdee6d0311afdc30074e96e3cd0bcb5c95b Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Fri, 25 Mar 2022 22:13:57 -0700 Subject: [PATCH 3/5] Fontify types in generic instantiation. Fontify type names in generic instantiation param lists. We look for index expressions and fontify the elements as types if one of the following is true: - There is more than one element in the list (e.g. "foo[int, string]"). - The list is followed by "{" (a composite literal) (e.g. "foo[int]{}"). - The list is preceded by space and then an identifer (e.g. "var foo bar[int]"). When in doubt, we don't fontify, so expressions like "foo[int]()" will not have "int" fontified as a type. --- go-mode.el | 70 ++++++++++++++++++++++++++++++++++++--- test/go-font-lock-test.el | 8 +++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/go-mode.el b/go-mode.el index 396b9ca3..ee51717c 100644 --- a/go-mode.el +++ b/go-mode.el @@ -453,6 +453,16 @@ statements." (1 font-lock-type-face nil t) (2 font-lock-type-face nil t)) + ;; Match type params in instantiation such as "foo[int, string]". + (go--match-type-param-start + ;; Sub-matcher that matches individual type params in the list. + (go--fontify-type-param + nil + ;; Post-matcher that moves point back to "[" so next match can find + ;; nested param lists. + (go--fontify-type-param-post) + (1 font-lock-type-face nil t))) + ;; An anchored matcher for type switch case clauses. (go--match-type-switch-case (go--fontify-type-switch-case @@ -1452,19 +1462,19 @@ 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) +(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." (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))) @@ -1496,7 +1506,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 () @@ -1633,6 +1643,56 @@ succeeds." (go--in-type-params-p)))))) found-match)) + +(defvar go--fontify-type-param-beg nil) + +(defun go--match-type-param-start (end) + "Search for [ that starts a type instantiation param list." + (let (found-match) + (while (and + (not found-match) + (search-forward "[" end t)) + (when (not (go-in-string-or-comment-p)) + (setq go--fontify-type-param-beg (point)) + + ;; In general an index expression is ambiguous, but there are some + ;; things we can look for to be certain it is a type param list. + (setq found-match + (save-excursion + (or + ;; If we have more than one comma, or a composite literal "{" + ;; follows the param list. + (let ((commas 0)) + (save-excursion + (while (go--search-next-comma end ?\]) + (cl-incf commas)) + (or (> commas 0) + (eq (char-after (1+ (point))) ?{)))) + + ;; If we are preceded by a space and an identifer then we are + ;; in type name (e.g. preceded be "foo " in "var foo bar[int]"). + (and (not (zerop (skip-syntax-backward "^ "))) + (not (zerop (skip-syntax-backward " " (line-beginning-position)))) + (let ((word (thing-at-point 'word 'no-properties))) + (and word (not (member word go-mode-keywords)))))))))) + found-match)) + +(defun go--fontify-type-param (end) + "Advance through each type param in a type instantion param list." + (let (found-match done) + (while (and (not found-match) (not done)) + (skip-syntax-forward " ") + (setq found-match (and + (looking-at go-type-name-regexp) + (not (member (match-string 1) go-mode-keywords)))) + ;; Advance to next comma. We are done if there are no more commas. + (setq done (not (go--search-next-comma end ?\])))) + found-match)) + +(defun go--fontify-type-param-post () + "Move point back to opening bracket to allow matching nested param lists." + (goto-char go--fontify-type-param-beg)) + (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 47d7ce58..9515ccc1 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -53,6 +53,14 @@ QKfuncK (VfV TintT) {} (go--should-fontify "KfuncK FfooF[a TintT | TstringT | KstructK{} | *Tfoo.ZebraT](TintT) { }") (go--should-fontify "KinterfaceK { TintT | Tfloat64T }")) +(ert-deftest go--fontify-type-instantiation () + ;; ambiguous - leave it unfontified + (go--should-fontify "foo[int]") + + (go--should-fontify "KvarK VvV TfooT[TintT]") + (go--should-fontify "KvarK VvV TfooT[TbarT[TintT]]") + (go--should-fontify "foo[TintT, KstructK{}, *Tfoo.ZebraT]")) + (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") (go--should-fontify "KstructK { a, b TintT }") From 28e26e0ff3e764cd62ede7b083914388525af788 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Sat, 26 Mar 2022 22:05:40 -0700 Subject: [PATCH 4/5] 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 | 35 ++++++++++++++++++++++++++++++++--- test/go-font-lock-test.el | 10 ++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/go-mode.el b/go-mode.el index ee51717c..76201a88 100644 --- a/go-mode.el +++ b/go-mode.el @@ -486,9 +486,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))) @@ -1730,6 +1730,35 @@ 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)) + (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 9515ccc1..20919b71 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -61,6 +61,16 @@ QKfuncK (VfV TintT) {} (go--should-fontify "KvarK VvV TfooT[TbarT[TintT]]") (go--should-fontify "foo[TintT, KstructK{}, *Tfoo.ZebraT]")) +(ert-deftest go--fontify-func () + (go--should-fontify "KfuncK FfooF()") + (go--should-fontify "KfuncK FfooF[A 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[TintT, TstringT](123)")) + (ert-deftest go--fontify-struct () (go--should-fontify "KstructK { i TintT }") (go--should-fontify "KstructK { a, b TintT }") From fef154a6c55943ea1fe10d7310b5889497047aaa Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Sun, 27 Mar 2022 15:19:53 -0700 Subject: [PATCH 5/5] Fix fontification of composite literals w/ type params. Fix "foo.Bar" to be fontified as a type in "foo.Bar[int, string]{}". --- go-mode.el | 23 ++++++++++++++++++++++- test/go-font-lock-test.el | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/go-mode.el b/go-mode.el index 76201a88..5f754442 100644 --- a/go-mode.el +++ b/go-mode.el @@ -506,7 +506,7 @@ statements." ("\\(!\\)[^=]" 1 font-lock-negation-char-face) ;; Composite literal type - (,(concat go-type-name-regexp "{") 1 font-lock-type-face) + (go--match-composite-literal 1 font-lock-type-face) ;; Map value type (go--match-map-value 1 font-lock-type-face) @@ -1758,6 +1758,27 @@ We are looking for the right-hand-side of the type alias" (eq (char-after) ?\()))))) 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--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 20919b71..afed6289 100644 --- a/test/go-font-lock-test.el +++ b/test/go-font-lock-test.el @@ -124,6 +124,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}")