emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/go-mode 3fb128a 219/495: Highlight type names in function


From: ELPA Syncer
Subject: [nongnu] elpa/go-mode 3fb128a 219/495: Highlight type names in function parameter list
Date: Sat, 7 Aug 2021 09:05:17 -0400 (EDT)

branch: elpa/go-mode
commit 3fb128afb93780e1b768b552137c950074e78129
Author: Rui Ueyama <ruiu@google.com>
Commit: Rui Ueyama <ruiu@google.com>

    Highlight type names in function parameter list
    
    Currently go-mode does not set font-lock-type-face to type
    names in a function parameter list. This patch is to set it
    to highlight them.
    
    Context matters to find type names in a parameter list. For
    example, X and Y are type names in "func(X, Y)" but are not
    in "func(X, Y int)", so we cannot say if they are type names
    until we see int or a closing parenthesis in this case. In
    this patch, we first scan a parameter list to see whether or
    not the list has names, and scan it again to get positions
    of type names.
    
    All "T"s (and not "x"s and "y"s) in the following code are
    highlighted with this patch.
    
      func (T)
      func (x T)
      func (x, y T, z T)
    
      func ([]T, T)
      func (*T, T)
    
      func (T) T
      func (T) *T
      func (T) (T)
      func (T) (*T)
      func (T) (T, T)
      func (T) (n T, err T)
      func (T) (m, n T)
    
      func fn(T)
      func fn(T, T, T)
      func fn(x T)
      func fn(x, y T, z T)
    
      func (T) method(T) T
      func (x T) method(T) (T, T)
      func (x T) method(T) (n T, err T)
    
      func (x func(x T), y func(T, T) T) func() T
    
    This change has already been reviewed at
    https://codereview.appspot.com/81240047
---
 go-mode.el | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 235 insertions(+), 13 deletions(-)

diff --git a/go-mode.el b/go-mode.el
index 3b55c98..23dd9ca 100644
--- a/go-mode.el
+++ b/go-mode.el
@@ -122,6 +122,8 @@
 
 (defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
 (defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
+(defconst go-type-name-no-prefix-regexp 
"\\(?:[[:word:][:multibyte:]]+\\.\\)?[[:word:][:multibyte:]]+")
+(defconst go-qualified-identifier-regexp (concat go-identifier-regexp "\\." 
go-identifier-regexp))
 (defconst go-label-regexp go-identifier-regexp)
 (defconst go-type-regexp "[[:word:][:multibyte:]*]+")
 (defconst go-func-regexp (concat (go--regexp-enclose-in-symbol "func") "\\s 
*\\(" go-identifier-regexp "\\)"))
@@ -131,6 +133,7 @@
                                "\\s *)\\s *\\)?\\("
                                go-identifier-regexp
                                "\\)("))
+
 (defconst go-builtins
   '("append" "cap"   "close"   "complex" "copy"
     "delete" "imag"  "len"     "make"    "new"
@@ -146,7 +149,11 @@
   "All keywords in the Go language.  Used for font locking.")
 
 (defconst go-constants '("nil" "true" "false" "iota"))
-(defconst go-type-name-regexp (concat "\\(?:[*(]\\)*\\(?:" 
go-identifier-regexp "\\.\\)?\\(" go-identifier-regexp "\\)"))
+(defconst go-type-name-regexp (concat "\\(?:[*(]\\)*\\(\\(?:" 
go-identifier-regexp "\\.\\)?" go-identifier-regexp "\\)"))
+
+;; Maximum number of identifiers that can be highlighted as type names
+;; in one function type/declaration.
+(defconst go--font-lock-func-param-num-groups 16)
 
 (defvar go-dangling-cache)
 (defvar go-godoc-history nil)
@@ -276,7 +283,10 @@ For mode=set, all covered lines will have this weight."
   ;; we cannot use 'symbols in regexp-opt because GNU Emacs <24
   ;; doesn't understand that
   (append
-   `((,(go--regexp-enclose-in-symbol (regexp-opt go-mode-keywords t)) . 
font-lock-keyword-face)
+   `((go--match-func
+      ,@(mapcar (lambda (x) `(,x font-lock-type-face))
+                (number-sequence 1 go--font-lock-func-param-num-groups)))
+     (,(go--regexp-enclose-in-symbol (regexp-opt go-mode-keywords t)) . 
font-lock-keyword-face)
      (,(concat "\\(" (go--regexp-enclose-in-symbol (regexp-opt go-builtins t)) 
"\\)[[:space:]]*(") 1 font-lock-builtin-face)
      (,(go--regexp-enclose-in-symbol (regexp-opt go-constants t)) . 
font-lock-constant-face)
      (,go-func-regexp 1 font-lock-function-name-face)) ;; function (not 
method) name
@@ -291,15 +301,13 @@ For mode=set, all covered lines will have this weight."
      (,(concat (go--regexp-enclose-in-symbol "type") 
"[[:space:]]+\\([^[:space:]]+\\)") 1 font-lock-type-face) ;; types
      (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]+" 
go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) 
;; types
      (,(concat "[^[:word:][:multibyte:]]\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" 
go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices
-     (,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face)
+     (,(concat "\\(" go-type-name-regexp "\\)" "{") 1 font-lock-type-face)
      (,(concat (go--regexp-enclose-in-symbol "map") "\\[[^]]+\\]" 
go-type-name-regexp) 1 font-lock-type-face) ;; map value type
      (,(concat (go--regexp-enclose-in-symbol "map") "\\[" go-type-name-regexp) 
1 font-lock-type-face) ;; map key type
      (,(concat (go--regexp-enclose-in-symbol "chan") 
"[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 
font-lock-type-face) ;; channel type
      (,(concat (go--regexp-enclose-in-symbol "\\(?:new\\|make\\)") 
"\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; 
new/make type
      ;; TODO do we actually need this one or isn't it just a function call?
      (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type 
conversion
-     (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" 
go-identifier-regexp "[[:space:]]+" go-type-name-regexp ")") 1 
font-lock-type-face) ;; Method receiver
-     (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" 
go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver without 
variable name
      ;; Like the original go-mode this also marks compound literal
      ;; fields. There, it was marked as to fix, but I grew quite
      ;; accustomed to it, so it'll stay for now.
@@ -385,14 +393,15 @@ STOP-AT-STRING is not true, over strings."
 (defun go--match-raw-string-literal (end)
   "Search for a raw string literal. Set point to the end of the
 occurence found on success. Returns nil on failure."
-  (when (search-forward "`" end t)
-    (goto-char (match-beginning 0))
-    (if (go-in-string-or-comment-p)
-        (progn (goto-char (match-end 0))
-               (go--match-raw-string-literal end))
-      (when (looking-at "\\(`\\)\\([^`]*\\)\\(`\\)")
-        (goto-char (match-end 0))
-        t))))
+  (unless (go-in-string-or-comment-p)
+    (when (search-forward "`" end t)
+      (goto-char (match-beginning 0))
+      (if (go-in-string-or-comment-p)
+          (progn (goto-char (match-end 0))
+                 (go--match-raw-string-literal end))
+        (when (looking-at "\\(`\\)\\([^`]*\\)\\(`\\)")
+          (goto-char (match-end 0))
+          t)))))
 
 (defun go-previous-line-has-dangling-op-p ()
   "Returns non-nil if the current line is a continuation line."
@@ -527,6 +536,219 @@ current line will be returned."
       (skip-chars-forward "^}")
       (forward-char))))
 
+(defun go--find-enclosing-parentheses (position)
+  "Return points of outermost '(' and ')' surrounding POSITION if
+such parentheses exist.
+
+If outermost '(' exists but ')' does not, it returns the next blank
+line or end-of-buffer position instead of the position of the closing
+parenthesis.
+
+If the starting parenthesis is not found, it returns (POSITION
+POSITION).
+"
+  (save-excursion
+    (let (beg end)
+      (goto-char position)
+      (while (> (go-paren-level) 0)
+        (re-search-backward "[(\\[{]" nil t)
+        (when (looking-at "(")
+          (setq beg (point))))
+      (if (null beg)
+          (list position position)
+        (goto-char position)
+        (while (and (> (go-paren-level) 0)
+                    (search-forward ")" nil t)))
+        (when (> (go-paren-level) 0)
+          (unless (re-search-forward "^[[:space:]]*$" nil t)
+            (goto-char (point-max))))
+        (list beg (point))))))
+
+(defun go--search-next-comma (end)
+  "Search forward from point for a comma whose nesting level is
+the same as point. If it reaches the end of line or a closing
+parenthesis before a comma, it stops at it."
+  (let ((orig-level (go-paren-level)))
+    (while (and (< (point) end)
+                (or (looking-at "[^,)\n]")
+                    (> (go-paren-level) orig-level)))
+      (forward-char))
+    (when (and (looking-at ",")
+               (< (point) (1- end)))
+      (forward-char))))
+
+(defun go--looking-at-keyword ()
+  (and (looking-at (concat "\\(" go-identifier-regexp "\\)"))
+       (member (match-string 1) go-mode-keywords)))
+
+(defun go--match-func (end)
+  "Search for identifiers used as type names from a function
+parameter list, and set the identifier positions as the results
+of last search. Return t if search succeeded."
+  (when (re-search-forward (go--regexp-enclose-in-symbol "func") end t)
+    (let ((regions (go--match-func-type-names end)))
+      (if (null regions)
+          ;; Nothing to highlight. This can happen if the current func
+          ;; is "func()". Try next one.
+          (go--match-func end)
+        ;; There are something to highlight. Set those positions as
+        ;; last search results.
+        (setq regions (go--filter-match-data regions end))
+        (when regions
+          (set-match-data (go--make-match-data regions))
+          t)))))
+
+(defun go--match-func-type-names (end)
+  (cond
+   ;; Function declaration (e.g. "func foo(")
+   ((looking-at (concat "[[:space:]\n]*" go-identifier-regexp 
"[[:space:]\n]*("))
+    (goto-char (match-end 0))
+    (nconc (go--match-parameter-list end)
+           (go--match-function-result end)))
+   ;; Method declaration, function literal, or function type
+   ((looking-at "[[:space:]]*(")
+    (goto-char (match-end 0))
+    (let ((regions (go--match-parameter-list end)))
+      ;; Method declaration (e.g. "func (x y) foo(")
+      (when (looking-at (concat "[[:space:]]*" go-identifier-regexp 
"[[:space:]\n]*("))
+        (goto-char (match-end 0))
+        (setq regions (nconc regions (go--match-parameter-list end))))
+      (nconc regions (go--match-function-result end))))))
+
+(defun go--parameter-list-type (end)
+  "Return 'present if the parameter list has names, or 'absent if
+not, assuming point is at the beginning of a parameter list, just
+after '('."
+  (save-excursion
+    (skip-chars-forward "[:space:]\n" end)
+    (cond ((> (point) end)
+           nil)
+          ((looking-at (concat go-identifier-regexp "[[:space:]\n]*,"))
+           (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]*\\(?:)\\|\\'\\)"))
+               (go--looking-at-keyword)
+               (looking-at "[*\\[]\\|\\.\\.\\.\\|\\'"))
+           'absent)
+          (t 'present))))
+
+(defconst go--opt-dotdotdot-regexp "\\(?:\\.\\.\\.\\)?")
+(defconst go--parameter-type-regexp
+  (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(" 
go-type-name-no-prefix-regexp "\\)[[:space:]\n]*\\([,)]\\|\\'\\)"))
+(defconst go--func-type-in-parameter-list-regexp
+  (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(" 
(go--regexp-enclose-in-symbol "func") "\\)"))
+
+(defun go--match-parameters-common (identifier-regexp end)
+  (let ((acc ())
+        (start -1))
+    (while (progn (skip-chars-forward "[:space:]\n" end)
+                  (and (not (looking-at "\\(?:)\\|\\'\\)"))
+                       (< start (point))
+                       (<= (point) end)))
+      (setq start (point))
+      (cond
+       ((looking-at (concat identifier-regexp go--parameter-type-regexp))
+        (setq acc (nconc acc (list (match-beginning 1) (match-end 1))))
+        (goto-char (match-beginning 2)))
+       ((looking-at (concat identifier-regexp 
go--func-type-in-parameter-list-regexp))
+        (goto-char (match-beginning 1))
+        (setq acc (nconc acc (go--match-func-type-names end)))
+        (go--search-next-comma end))
+       (t
+        (go--search-next-comma end))))
+    (when (and (looking-at ")")
+               (< (point) end))
+      (forward-char))
+    acc))
+
+(defun go--match-parameters-with-identifier-list (end)
+  (go--match-parameters-common
+   (concat go-identifier-regexp "[[:space:]\n]+")
+   end))
+
+(defun go--match-parameters-without-identifier-list (end)
+  (go--match-parameters-common "" end))
+
+(defun go--filter-match-data (regions end)
+  "Remove points from regions if they are beyond end. Regions are
+a list whose size is multiple of 2. Element 2n is beginning of a
+region and 2n+1 is end of it.
+
+This function is used to make sure we don't override end point
+that font-lock-mode gave to us."
+  (when regions
+    (let* ((vec (vconcat regions))
+           (i 0)
+           (len (length vec)))
+      (while (and (< i len)
+                  (<= (nth i regions) end)
+                  (<= (nth (1+ i) regions) end))
+        (setq i (+ i 2)))
+      (cond ((= i len)
+             regions)
+            ((zerop i)
+             nil)
+            (t
+             (setcdr (nth i regions) nil)
+             regions)))))
+
+(defun go--make-match-data (regions)
+  (let ((deficit (- (* 2 go--font-lock-func-param-num-groups)
+                    (length regions))))
+    (when (> deficit 0)
+      (let ((last (car (last regions))))
+        (setq regions (nconc regions (make-list deficit last))))))
+  `(,(car regions) ,@(last regions) ,@regions))
+
+(defun go--match-parameter-list (end)
+  "Returns a list of identifier positions that are used as type
+names in a function parameter list, assuming point is at the
+beginning of a parameter list. Returns nil if the text after
+point does not look like a parameter list.
+
+Set point to end of closing parenthesis on success.
+
+In Go, the names must either all be present or all be absent
+within a list of parameters.
+
+Parsing a parameter list is a little bit complicated because we
+have to scan through the parameter list to determine whether or
+not the list has names. Until a type name is found or reaching
+end of a parameter list, we are not sure which form the parameter
+list is.
+
+For example, X and Y are type names in a parameter list \"(X,
+Y)\" but are parameter names in \"(X, Y int)\". We cannot say if
+X is a type name until we see int after Y.
+
+Note that even \"(int, float T)\" is a valid parameter
+list. Builtin type names are not reserved words. In this example,
+int and float are parameter names and only T is a type name.
+
+In this function, we first scan the parameter list to see if the
+list has names, and then handle it accordingly."
+  (let ((name (go--parameter-list-type end)))
+    (cond ((eq name 'present)
+           (go--match-parameters-with-identifier-list end))
+          ((eq name 'absent)
+           (go--match-parameters-without-identifier-list end))
+          (t nil))))
+
+(defun go--match-function-result (end)
+  "Returns a list of identifier positions that are used as type
+names in a function result, assuming point is at the beginning of
+a result.
+
+Function result is a unparenthesized type or a parameter list."
+  (cond ((and (looking-at (concat "[[:space:]*]*\\(" 
go-type-name-no-prefix-regexp "\\)"))
+              (not (member (match-string 1) go-mode-keywords)))
+         (list (match-beginning 1) (match-end 1)))
+        ((looking-at "[[:space:]]*(")
+         (goto-char (match-end 0))
+         (go--match-parameter-list end))
+        (t nil)))
+
 ;;;###autoload
 (define-derived-mode go-mode prog-mode "Go"
   "Major mode for editing Go source text.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]