Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 174 additions & 25 deletions go-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,21 @@ 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))

;; 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
Expand All @@ -471,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)))

Expand All @@ -491,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)
Expand Down Expand Up @@ -733,6 +748,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
Expand Down Expand Up @@ -1375,24 +1400,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 "\\_<func" (- (point) 4)))))))))
(backward-char)

;; If we see a type param list, jump backwards over it.
(when (eq (char-before) ?\])
(backward-char)
(go-goto-opening-parenthesis))

(setq found-match
(or
(looking-back
;; We are either preceeded by "func", "func foo", ") ", or ") foo".
(concat "\\(\\_<func\\s-*\\|)\\s-+\\)\\(?:" go-identifier-regexp "\\)?")
(line-beginning-position))
(go--in-interface-p))))))
found-match))


Expand Down Expand Up @@ -1438,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)))
Expand Down Expand Up @@ -1482,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 ()
Expand Down Expand Up @@ -1594,6 +1618,81 @@ 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))


(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)
Expand Down Expand Up @@ -1631,6 +1730,56 @@ 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--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 "\\_<map\\_>\\[\\(?:\\[[^]]*\\]\\)*[^]]*\\]" go-type-name-regexp))

Expand Down
27 changes: 27 additions & 0 deletions test/go-font-lock-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@ QD// DQ(
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) { }"))

(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-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-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 }")
Expand Down Expand Up @@ -98,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}")
Expand Down