]> git.bundesbrandschatzamt.de Git - emacsconfig.git/commitdiff
update go
authorAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sat, 25 Nov 2023 10:35:51 +0000 (11:35 +0100)
committerAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sun, 26 Nov 2023 15:44:23 +0000 (16:44 +0100)
Signed-off-by: Andreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
18 files changed:
elpa/go-mode-20191029.2119/go-mode-autoloads.el [deleted file]
elpa/go-mode-20191029.2119/go-mode-pkg.el [deleted file]
elpa/go-mode-20191029.2119/go-mode.el [deleted file]
elpa/go-mode-20230624.2315/go-mode-autoloads.el [new file with mode: 0644]
elpa/go-mode-20230624.2315/go-mode-pkg.el [new file with mode: 0644]
elpa/go-mode-20230624.2315/go-mode.el [new file with mode: 0644]
elpa/go-translate-20230503.303/go-translate-autoloads.el [new file with mode: 0644]
elpa/go-translate-20230503.303/go-translate-pkg.el [new file with mode: 0644]
elpa/go-translate-20230503.303/go-translate.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-core.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-engine-bing.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-engine-deepl.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-engine-google-rpc.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-engine-google.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-engine-stardict.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-engine-youdao.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-faces.el [new file with mode: 0644]
elpa/go-translate-20230503.303/gts-implements.el [new file with mode: 0644]

diff --git a/elpa/go-mode-20191029.2119/go-mode-autoloads.el b/elpa/go-mode-20191029.2119/go-mode-autoloads.el
deleted file mode 100644 (file)
index 72c1c98..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-;;; go-mode-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))
-\f
-;;;### (autoloads nil "go-mode" "go-mode.el" (23994 46478 910207
-;;;;;;  727000))
-;;; Generated autoloads from go-mode.el
-
-(autoload 'go-mode "go-mode" "\
-Major mode for editing Go source text.
-
-This mode provides (not just) basic editing capabilities for
-working with Go code. It offers almost complete syntax
-highlighting, indentation that is almost identical to gofmt and
-proper parsing of the buffer content to allow features such as
-navigation by function, manipulation of comments or detection of
-strings.
-
-In addition to these core features, it offers various features to
-help with writing Go code. You can directly run buffer content
-through gofmt, read godoc documentation from within Emacs, modify
-and clean up the list of package imports or interact with the
-Playground (uploading and downloading pastes).
-
-The following extra functions are defined:
-
-- `gofmt'
-- `godoc' and `godoc-at-point'
-- `go-import-add'
-- `go-remove-unused-imports'
-- `go-goto-arguments'
-- `go-goto-docstring'
-- `go-goto-function'
-- `go-goto-function-name'
-- `go-goto-imports'
-- `go-goto-return-values'
-- `go-goto-method-receiver'
-- `go-play-buffer' and `go-play-region'
-- `go-download-play'
-- `godef-describe' and `godef-jump'
-- `go-coverage'
-- `go-set-project'
-- `go-reset-gopath'
-
-If you want to automatically run `gofmt' before saving a file,
-add the following hook to your emacs configuration:
-
-\(add-hook 'before-save-hook #'gofmt-before-save)
-
-If you want to use `godef-jump' instead of etags (or similar),
-consider binding godef-jump to `M-.', which is the default key
-for `find-tag':
-
-\(add-hook 'go-mode-hook (lambda ()
-                          (local-set-key (kbd \"M-.\") #'godef-jump)))
-
-Please note that godef is an external dependency. You can install
-it with
-
-go get github.com/rogpeppe/godef
-
-
-If you're looking for even more integration with Go, namely
-on-the-fly syntax checking, auto-completion and snippets, it is
-recommended that you look at flycheck
-\(see URL `https://github.com/flycheck/flycheck') or flymake in combination
-with goflymake (see URL `https://github.com/dougm/goflymake'), gocode
-\(see URL `https://github.com/nsf/gocode'), go-eldoc
-\(see URL `github.com/syohex/emacs-go-eldoc') and yasnippet-go
-\(see URL `https://github.com/dominikh/yasnippet-go')
-
-\(fn)" t nil)
-
-(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
-
-(autoload 'gofmt-before-save "go-mode" "\
-Add this to .emacs to run gofmt on the current buffer when saving:
-\(add-hook 'before-save-hook 'gofmt-before-save).
-
-Note that this will cause ‘go-mode’ to get loaded the first time
-you save any file, kind of defeating the point of autoloading.
-
-\(fn)" t nil)
-
-(autoload 'godoc "go-mode" "\
-Show Go documentation for QUERY, much like \\<go-mode-map>\\[man].
-
-\(fn QUERY)" t nil)
-
-(autoload 'go-download-play "go-mode" "\
-Download a paste from the playground and insert it in a Go buffer.
-Tries to look for a URL at point.
-
-\(fn URL)" t nil)
-
-(autoload 'go-dot-mod-mode "go-mode" "\
-A major mode for editing go.mod files.
-
-\(fn)" t nil)
-
-(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-dot-mod-mode))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; End:
-;;; go-mode-autoloads.el ends here
diff --git a/elpa/go-mode-20191029.2119/go-mode-pkg.el b/elpa/go-mode-20191029.2119/go-mode-pkg.el
deleted file mode 100644 (file)
index 9c6df33..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-;;; -*- no-byte-compile: t -*-
-(define-package "go-mode" "20191029.2119" "Major mode for the Go programming language" 'nil :commit "bd2f6d21196da95447c330cca7d35fb0a6798b4a" :keywords '("languages" "go") :authors '(("The go-mode Authors")) :maintainer '("The go-mode Authors") :url "https://github.com/dominikh/go-mode.el")
diff --git a/elpa/go-mode-20191029.2119/go-mode.el b/elpa/go-mode-20191029.2119/go-mode.el
deleted file mode 100644 (file)
index 5fa76ee..0000000
+++ /dev/null
@@ -1,2741 +0,0 @@
-;;; go-mode.el --- Major mode for the Go programming language
-
-;;; Commentary:
-
-;; Copyright 2013 The go-mode Authors.  All rights reserved.
-;; Use of this source code is governed by a BSD-style
-;; license that can be found in the LICENSE file.
-
-;; Author: The go-mode Authors
-;; Version: 1.5.0
-;; Package-Version: 20191029.2119
-;; Keywords: languages go
-;; URL: https://github.com/dominikh/go-mode.el
-;;
-;; This file is not part of GNU Emacs.
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'compile)
-(require 'etags)
-(require 'ffap)
-(require 'find-file)
-(require 'ring)
-(require 'url)
-(require 'xref nil :noerror)  ; xref is new in Emacs 25.1
-
-
-(eval-when-compile
-  (defmacro go--forward-word (&optional arg)
-   (if (fboundp 'forward-word-strictly)
-       `(forward-word-strictly ,arg)
-     `(forward-word ,arg))))
-
-(defun go--delete-whole-line (&optional arg)
-  "Delete the current line without putting it in the `kill-ring'.
-Derived from function `kill-whole-line'.  ARG is defined as for that
-function."
-  (setq arg (or arg 1))
-  (if (and (> arg 0)
-           (eobp)
-           (save-excursion (forward-visible-line 0) (eobp)))
-      (signal 'end-of-buffer nil))
-  (if (and (< arg 0)
-           (bobp)
-           (save-excursion (end-of-visible-line) (bobp)))
-      (signal 'beginning-of-buffer nil))
-  (cond ((zerop arg)
-         (delete-region (progn (forward-visible-line 0) (point))
-                        (progn (end-of-visible-line) (point))))
-        ((< arg 0)
-         (delete-region (progn (end-of-visible-line) (point))
-                        (progn (forward-visible-line (1+ arg))
-                               (unless (bobp)
-                                 (backward-char))
-                               (point))))
-        (t
-         (delete-region (progn (forward-visible-line 0) (point))
-                        (progn (forward-visible-line arg) (point))))))
-
-(defun go-goto-opening-parenthesis (&optional _legacy-unused)
-  "Move up one level of parentheses.
-
-Return non-nil if there was a paren to move up to."
-  ;; The old implementation of go-goto-opening-parenthesis had an
-  ;; optional argument to speed up the function.  It didn't change the
-  ;; function's outcome.
-
-  ;; Silently fail if there's no matching opening parenthesis.
-  (let ((open-char (nth 1 (syntax-ppss))))
-    (when open-char
-      (goto-char open-char))))
-
-
-(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
-(defconst go--max-dangling-operator-length 2
-  "The maximum length of dangling operators.
-This must be at least the length of the longest string matched by
-‘go-dangling-operators-regexp’ and must be updated whenever that
-constant is changed.")
-
-(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 "\\_<func\\_>\\s *\\(" go-identifier-regexp "\\)"))
-(defconst go-func-meth-regexp (concat
-                               "\\_<func\\_>\\s *\\(?:(\\s *"
-                               "\\(" go-identifier-regexp "\\s +\\)?" go-type-regexp
-                               "\\s *)\\s *\\)?\\("
-                               go-identifier-regexp
-                               "\\)("))
-
-(defconst go-builtins
-  '("append" "cap"   "close"   "complex" "copy"
-    "delete" "imag"  "len"     "make"    "new"
-    "panic"  "print" "println" "real"    "recover")
-  "All built-in functions in the Go language.  Used for font locking.")
-
-(defconst go-mode-keywords
-  '("break"    "default"     "func"   "interface" "select"
-    "case"     "defer"       "go"     "map"       "struct"
-    "chan"     "else"        "goto"   "package"   "switch"
-    "const"    "fallthrough" "if"     "range"     "type"
-    "continue" "for"         "import" "return"    "var")
-  "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 "\\)"))
-
-(defvar go-dangling-cache)
-(defvar go-godoc-history nil)
-(defvar go--coverage-current-file-name)
-
-(defgroup go nil
-  "Major mode for editing Go code."
-  :link '(url-link "https://github.com/dominikh/go-mode.el")
-  :group 'languages)
-
-(defgroup go-cover nil
-  "Options specific to `cover`."
-  :group 'go)
-
-(defgroup godoc nil
-  "Options specific to `godoc'."
-  :group 'go)
-
-(defcustom go-fontify-function-calls t
-  "Fontify function and method calls if this is non-nil."
-  :type 'boolean
-  :group 'go)
-
-(defcustom go-mode-hook nil
-  "Hook called by `go-mode'."
-  :type 'hook
-  :group 'go)
-
-(defcustom go-command "go"
-  "The 'go' command.
-Some users have multiple Go development trees and invoke the 'go'
-tool via a wrapper that sets GOROOT and GOPATH based on the
-current directory.  Such users should customize this variable to
-point to the wrapper script."
-  :type 'string
-  :group 'go)
-
-(defcustom gofmt-command "gofmt"
-  "The 'gofmt' command.
-Some users may replace this with 'goimports'
-from https://golang.org/x/tools/cmd/goimports."
-  :type 'string
-  :group 'go)
-
-(defcustom gofmt-args nil
-  "Additional arguments to pass to gofmt."
-  :type '(repeat string)
-  :group 'go)
-
-(defcustom gofmt-show-errors 'buffer
-  "Where to display gofmt error output.
-It can either be displayed in its own buffer, in the echo area, or not at all.
-
-Please note that Emacs outputs to the echo area when writing
-files and will overwrite gofmt's echo output if used from inside
-a `before-save-hook'."
-  :type '(choice
-          (const :tag "Own buffer" buffer)
-          (const :tag "Echo area" echo)
-          (const :tag "None" nil))
-  :group 'go)
-
-(defcustom godef-command "godef"
-  "The 'godef' command."
-  :type 'string
-  :group 'go)
-
-(defcustom go-other-file-alist
-  '(("_test\\.go\\'" (".go"))
-    ("\\.go\\'" ("_test.go")))
-  "See the documentation of `ff-other-file-alist' for details."
-  :type '(repeat (list regexp (choice (repeat string) function)))
-  :group 'go)
-
-(defcustom go-packages-function 'go-packages-go-list
-  "Function called by `go-packages' to determine the list of available packages.
-This is used in e.g. tab completion in `go-import-add'.
-
-This package provides two functions: `go-packages-go-list' uses
-'go list all' to determine all Go packages. `go-packages-native' uses
-elisp to find all .a files in all /pkg/ directories.
-`go-packages-native' is obsolete as it doesn't behave correctly with
-the Go build cache or Go modules."
-  :type 'function
-  :package-version '(go-mode . 1.4.0)
-  :group 'go)
-
-(defcustom go-guess-gopath-functions (list #'go-godep-gopath
-                                           #'go-wgo-gopath
-                                           #'go-gb-gopath
-                                           #'go-plain-gopath)
-  "Functions to call in sequence to detect a project's GOPATH.
-
-The functions in this list will be called one after another,
-until a function returns non-nil.  The order of the functions in
-this list is important, as some project layouts may superficially
-look like others.  For example, a subset of wgo projects look like
-gb projects.  That's why we need to detect wgo first, to avoid
-mis-identifying them as gb projects."
-  :type '(repeat function)
-  :group 'go)
-
-(defcustom go-confirm-playground-uploads t
-  "Ask before uploading code to the public Go Playground.
-
-Set this to nil to upload without prompting.
-"
-  :type 'boolean
-  :group 'go)
-
-(defcustom godoc-command "go doc"
-  "Which executable to use for `godoc'.
-This can be either an absolute path or an executable in PATH."
-  :type 'string
-  :group 'go)
-
-(defcustom godoc-and-godef-command "go doc"
-  "Which executable to use for `godoc-and-godef'.
-This can be either an absolute path or an executable in PATH."
-  :type 'string
-  :group 'go)
-
-(defcustom godoc-use-completing-read nil
-  "Provide auto-completion for godoc.
-Only really desirable when using `godoc' instead of `go doc'."
-  :type 'boolean
-  :group 'godoc)
-
-(defcustom godoc-reuse-buffer nil
-  "Reuse a single *godoc* buffer to display godoc-at-point calls.
-The default behavior is to open a separate buffer for each call."
-  :type 'boolean
-  :group 'godoc)
-
-(defcustom godoc-at-point-function #'godoc-and-godef
-  "Function to call to display the documentation for an
-identifier at a given position.
-
-This package provides two functions: `godoc-and-godef' uses a
-combination of godef and godoc to find the documentation.  This
-approach has several caveats.  See its documentation for more
-information.  The second function, `godoc-gogetdoc' uses an
-additional tool that correctly determines the documentation for
-any identifier.  It provides better results than
-`godoc-and-godef'."
-  :type 'function
-  :group 'godoc)
-
-(defun godoc-and-godef (point)
-  "Use a combination of godef and godoc to guess the documentation at POINT.
-
-Due to a limitation in godoc, it is not possible to differentiate
-between functions and methods, which may cause `godoc-at-point'
-to display more documentation than desired.  Furthermore, it
-doesn't work on package names or variables.
-
-Consider using ‘godoc-gogetdoc’ instead for more accurate results."
-  (condition-case nil
-      (let* ((output (godef--call point))
-             (file (car output))
-             (name-parts (split-string (cadr output) " "))
-             (first (car name-parts)))
-        (if (not (godef--successful-p file))
-            (message "%s" (godef--error file))
-          (go--godoc (format "%s %s"
-                         (file-name-directory file)
-                         (if (or (string= first "type") (string= first "const"))
-                             (cadr name-parts)
-                           (car name-parts)))
-                    godoc-and-godef-command)))
-    (file-error (message "Could not run godef binary"))))
-
-(defun godoc-gogetdoc (point)
-  "Use the gogetdoc tool to find the documentation for an identifier at POINT.
-
-You can install gogetdoc with 'go get -u github.com/zmb3/gogetdoc'."
-  (if (not (buffer-file-name (go--coverage-origin-buffer)))
-      ;; TODO: gogetdoc supports unsaved files, but not introducing
-      ;; new artifical files, so this limitation will stay for now.
-      (error "Cannot use gogetdoc on a buffer without a file name"))
-  (let ((posn (format "%s:#%d" (file-truename buffer-file-name) (1- (position-bytes point))))
-        (out (godoc--get-buffer "<at point>")))
-  (with-current-buffer (get-buffer-create "*go-gogetdoc-input*")
-    (setq buffer-read-only nil)
-    (erase-buffer)
-    (go--insert-modified-files)
-    (call-process-region (point-min) (point-max) "gogetdoc" nil out nil
-                         "-modified"
-                         (format "-pos=%s" posn)))
-  (with-current-buffer out
-    (goto-char (point-min))
-    (godoc-mode)
-    (display-buffer (current-buffer) t))))
-
-(defun go--kill-new-message (url)
-  "Make URL the latest kill and print a message."
-  (kill-new url)
-  (message "%s" url))
-
-(defcustom go-play-browse-function 'go--kill-new-message
-  "Function to call with the Playground URL.
-See `go-play-region' for more details."
-  :type '(choice
-          (const :tag "Nothing" nil)
-          (const :tag "Kill + Message" go--kill-new-message)
-          (const :tag "Browse URL" browse-url)
-          (function :tag "Call function"))
-  :group 'go)
-
-(defcustom go-coverage-display-buffer-func 'display-buffer-reuse-window
-  "How `go-coverage' should display the coverage buffer.
-See `display-buffer' for a list of possible functions."
-  :type 'function
-  :group 'go-cover)
-
-(defface go-coverage-untracked
-  '((t (:foreground "#505050")))
-  "Coverage color of untracked code."
-  :group 'go-cover)
-
-(defface go-coverage-0
-  '((t (:foreground "#c00000")))
-  "Coverage color for uncovered code."
-  :group 'go-cover)
-(defface go-coverage-1
-  '((t (:foreground "#808080")))
-  "Coverage color for covered code with weight 1."
-  :group 'go-cover)
-(defface go-coverage-2
-  '((t (:foreground "#748c83")))
-  "Coverage color for covered code with weight 2."
-  :group 'go-cover)
-(defface go-coverage-3
-  '((t (:foreground "#689886")))
-  "Coverage color for covered code with weight 3."
-  :group 'go-cover)
-(defface go-coverage-4
-  '((t (:foreground "#5ca489")))
-  "Coverage color for covered code with weight 4."
-  :group 'go-cover)
-(defface go-coverage-5
-  '((t (:foreground "#50b08c")))
-  "Coverage color for covered code with weight 5."
-  :group 'go-cover)
-(defface go-coverage-6
-  '((t (:foreground "#44bc8f")))
-  "Coverage color for covered code with weight 6."
-  :group 'go-cover)
-(defface go-coverage-7
-  '((t (:foreground "#38c892")))
-  "Coverage color for covered code with weight 7."
-  :group 'go-cover)
-(defface go-coverage-8
-  '((t (:foreground "#2cd495")))
-  "Coverage color for covered code with weight 8.
-For mode=set, all covered lines will have this weight."
-  :group 'go-cover)
-(defface go-coverage-9
-  '((t (:foreground "#20e098")))
-  "Coverage color for covered code with weight 9."
-  :group 'go-cover)
-(defface go-coverage-10
-  '((t (:foreground "#14ec9b")))
-  "Coverage color for covered code with weight 10."
-  :group 'go-cover)
-(defface go-coverage-covered
-  '((t (:foreground "#2cd495")))
-  "Coverage color of covered code."
-  :group 'go-cover)
-
-(defvar go-mode-syntax-table
-  (let ((st (make-syntax-table)))
-    (modify-syntax-entry ?+  "." st)
-    (modify-syntax-entry ?-  "." st)
-    (modify-syntax-entry ?%  "." st)
-    (modify-syntax-entry ?&  "." st)
-    (modify-syntax-entry ?|  "." st)
-    (modify-syntax-entry ?^  "." st)
-    (modify-syntax-entry ?!  "." st)
-    (modify-syntax-entry ?=  "." st)
-    (modify-syntax-entry ?<  "." st)
-    (modify-syntax-entry ?>  "." st)
-    (modify-syntax-entry ?/  ". 124b" st)
-    (modify-syntax-entry ?*  ". 23" st)
-    (modify-syntax-entry ?\n "> b" st)
-    (modify-syntax-entry ?\" "\"" st)
-    (modify-syntax-entry ?\' "\"" st)
-    (modify-syntax-entry ?`  "\"" st)
-    (modify-syntax-entry ?\\ "\\" st)
-    ;; TODO make _ a symbol constituent now that xemacs is gone
-    (modify-syntax-entry ?_  "w" st)
-
-    st)
-  "Syntax table for Go mode.")
-
-(defun go--build-font-lock-keywords ()
-  ;; we cannot use 'symbols in regexp-opt because GNU Emacs <24
-  ;; doesn't understand that
-  (append
-   `(
-     ;; Match param lists in func signatures. This uses the
-     ;; MATCH-ANCHORED format (see `font-lock-keywords' docs).
-     ;;
-     ;; Parent/anchor match. It matches the param list opening "(".
-     (go--match-param-start
-      ;; Sub-matcher that matches individual params in the param list.
-      (go--fontify-param
-       ;; Pre-match form that runs before the first sub-match.
-       (go--fontify-param-pre)
-       nil ;; We don't use the post-match form.
-       ;; Subexp 1 is the param variable name, if any.
-       (1 font-lock-variable-name-face)
-       ;; Subexp 2 is the param type name, if any. We set the LAXMATCH
-       ;; flag to allow optional regex groups.
-       (2 font-lock-type-face nil t)))
-
-     ;; Special case to match non-parenthesized function results. For
-     ;; example, "func(i int) string".
-     (,(concat ")[[:space:]]+" go-type-name-regexp "\\([[:space:]]\\|$\\)") 1 font-lock-type-face)
-
-     ;; Match name+type pairs, such as "foo bar" in "var foo bar".
-     (go--match-ident-type-pair 2 font-lock-type-face)
-
-     ;; An anchored matcher for type switch case clauses.
-     (go--match-type-switch-case (go--fontify-type-switch-case nil nil (1 font-lock-type-face)))
-
-     ;; Match variable names in var decls.
-     (go--match-var-decl 1 font-lock-variable-name-face)
-
-     (,(concat "\\_<" (regexp-opt go-mode-keywords t) "\\_>") . font-lock-keyword-face)
-     (,(concat "\\(\\_<" (regexp-opt go-builtins t) "\\_>\\)[[:space:]]*(") 1 font-lock-builtin-face)
-     (,(concat "\\_<" (regexp-opt go-constants t) "\\_>") . font-lock-constant-face)
-
-     ;; Function (not method) name
-     (,go-func-regexp 1 font-lock-function-name-face))
-
-   (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))
-     ;; Method name
-     `((,go-func-meth-regexp 2 font-lock-function-name-face)))
-
-   `(
-     ;; Raw string literal, needed for font-lock-syntactic-keywords
-     ("\\(`[^`]*`\\)" 1 font-lock-multiline)
-
-     ;; Type decl
-     (,(concat "\\_<type\\_>[[:space:]]+\\(" go-identifier-regexp "\\)[[:space:]]*\\(?:=[[:space:]]*\\)?\\(?:" go-type-name-regexp "\\)?" ) (1 font-lock-type-face) (2 font-lock-type-face nil t))
-
-     ;; Arrays/slices: []<type> | [123]<type> | [some.Const]<type> | [someConst]<type> | [...]<type>
-     (,(concat "\\[\\(?:[[:digit:]]+\\|" go-qualified-identifier-regexp "\\|" go-identifier-regexp "\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 1 font-lock-type-face)
-
-     ;; Unary "!"
-     ("\\(!\\)[^=]" 1 font-lock-negation-char-face)
-
-     ;; Composite literal type
-     (,(concat go-type-name-regexp "{") 1 font-lock-type-face)
-
-     ;; Map value type
-     (,(concat "\\_<map\\_>\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face)
-
-     ;; Map key type
-     (,(concat "\\_<map\\_>\\[" go-type-name-regexp) 1 font-lock-type-face)
-
-     ;; Channel type
-     (,(concat "\\_<chan\\_>[[: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)
-
-     ;; Type assertion
-     (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face)
-
-     ;; Labels and compound literal fields
-     (,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face)
-
-     ;; Labels in goto/break/continue
-     (,(concat "\\_<\\(?:goto\\|break\\|continue\\)\\_>[[:space:]]*\\(" go-label-regexp "\\)") 1 font-lock-constant-face))))
-
-(let ((m (define-prefix-command 'go-goto-map)))
-  (define-key m "a" #'go-goto-arguments)
-  (define-key m "d" #'go-goto-docstring)
-  (define-key m "f" #'go-goto-function)
-  (define-key m "i" #'go-goto-imports)
-  (define-key m "m" #'go-goto-method-receiver)
-  (define-key m "n" #'go-goto-function-name)
-  (define-key m "r" #'go-goto-return-values))
-
-(defvar go-mode-map
-  (let ((m (make-sparse-keymap)))
-    (unless (boundp 'electric-indent-chars)
-      (define-key m "}" #'go-mode-insert-and-indent)
-      (define-key m ")" #'go-mode-insert-and-indent))
-    (define-key m (kbd "C-c C-a") #'go-import-add)
-    (define-key m (kbd "C-c C-j") #'godef-jump)
-    (define-key m (kbd "C-x 4 C-c C-j") #'godef-jump-other-window)
-    (define-key m (kbd "C-c C-d") #'godef-describe)
-    (define-key m (kbd "C-c C-f") 'go-goto-map)
-    m)
-  "Keymap used by ‘go-mode’.")
-
-(easy-menu-define go-mode-menu go-mode-map
-  "Menu for Go mode."
-  '("Go"
-    ["Describe Expression"   godef-describe t]
-    ["Jump to Definition"    godef-jump t]
-    "---"
-    ["Add Import"            go-import-add t]
-    ["Remove Unused Imports" go-remove-unused-imports t]
-    ["Go to Imports"         go-goto-imports t]
-    "---"
-    ("Playground"
-     ["Send Buffer"          go-play-buffer t]
-     ["Send Region"          go-play-region t]
-     ["Download"             go-download-play t])
-    "---"
-    ["Coverage"              go-coverage t]
-    ["Gofmt"                 gofmt t]
-    ["Godoc"                 godoc t]
-    "---"
-    ["Customize Mode"        (customize-group 'go) t]))
-
-(defun go-mode-insert-and-indent (key)
-  "Invoke the global binding of KEY, then reindent the line."
-
-  (interactive (list (this-command-keys)))
-  (call-interactively (lookup-key (current-global-map) key))
-  (indent-according-to-mode))
-
-(defmacro go-paren-level ()
-  `(car (syntax-ppss)))
-
-(defmacro go-in-string-or-comment-p ()
-  `(nth 8 (syntax-ppss)))
-
-(defmacro go-in-string-p ()
-  `(nth 3 (syntax-ppss)))
-
-(defmacro go-in-comment-p ()
-  `(nth 4 (syntax-ppss)))
-
-(defmacro go-goto-beginning-of-string-or-comment ()
-  `(goto-char (nth 8 (syntax-ppss))))
-
-(defun go--backward-irrelevant (&optional stop-at-string)
-  "Skip backwards over any characters that are irrelevant for
-indentation and related tasks.
-
-It skips over whitespace, comments, cases and labels and, if
-STOP-AT-STRING is not true, over strings."
-
-  (let (pos (start-pos (point)))
-    (skip-chars-backward "\n\s\t")
-    (if (and (save-excursion (beginning-of-line) (go-in-string-p))
-             (= (char-before) ?`)
-             (not stop-at-string))
-        (backward-char))
-    (if (and (go-in-string-p)
-             (not stop-at-string))
-        (go-goto-beginning-of-string-or-comment))
-    (if (looking-back "\\*/" (line-beginning-position))
-        (backward-char))
-    (if (go-in-comment-p)
-        (go-goto-beginning-of-string-or-comment))
-    (setq pos (point))
-    (beginning-of-line)
-    (if (or (looking-at (concat "^" go-label-regexp ":"))
-            (looking-at "^[[:space:]]*\\(case .+\\|default\\):"))
-        (end-of-line 0)
-      (goto-char pos))
-    (if (/= start-pos (point))
-        (go--backward-irrelevant stop-at-string))
-    (/= start-pos (point))))
-
-(defun go--buffer-narrowed-p ()
-  "Return non-nil if the current buffer is narrowed."
-  (/= (buffer-size)
-      (- (point-max)
-         (point-min))))
-
-(defun go-previous-line-has-dangling-op-p ()
-  "Return non-nil if the current line is a continuation line.
-The return value is cached based on the current `line-beginning-position'."
-  (let* ((line-begin (line-beginning-position))
-         (val (gethash line-begin go-dangling-cache 'nope)))
-    (when (or (go--buffer-narrowed-p) (equal val 'nope))
-      (save-excursion
-        (go--forward-line -1)
-        (if (go--current-line-has-dangling-op-p)
-            (setq val (line-end-position))
-          (setq val nil))
-
-        (if (not (go--buffer-narrowed-p))
-            (puthash line-begin val go-dangling-cache))))
-    val))
-
-(defun go--current-line-has-dangling-op-p ()
-  "Return non-nil if current line ends in a dangling operator.
-The return value is not cached."
-  (or
-   (and
-    (go--line-suffix-p go-dangling-operators-regexp)
-
-    ;; "=" does not behave like a dangling operator in decl statements.
-    (not (go--line-suffix-p "\\(?:var\\|type\\|const\\)[[:space:]].*="))
-
-    ;; Don't mistake "1234." for a dangling operator.
-    (not (go--line-suffix-p "[[:space:]]-?[[:digit:]][_0-9]*\\.")))
-
-   ;; treat comma as dangling operator in certain cases
-   (and
-    (go--line-suffix-p ",")
-    (save-excursion (end-of-line) (go--commas-indent-p)))))
-
-
-(defun go--commas-indent-p ()
-  "Return non-nil if in a context where dangling commas indent next line."
-  (not (or
-        (go--open-paren-position)
-        (go--in-composite-literal-p)
-        (go--in-case-clause-list-p)
-        (go--in-struct-definition-p))))
-
-(defun go--in-case-clause-list-p ()
-  "Return non-nil if inside a multi-line case cause list.
-
-This function is only concerned with list items on lines after the
-case keyword. It returns nil for the case line itself."
-  (save-excursion
-    (beginning-of-line)
-    (when (not (looking-at go--case-or-default-regexp))
-      (let (saw-colon)
-        ;; optionally skip line with the colon
-        (when (go--line-suffix-p ":")
-          (setq saw-colon t)
-          (forward-line -1))
-
-        ;; go backwards while at a comment or a line ending in comma
-        (while (and
-                (or
-                 (go--boring-line-p)
-                 (go--line-suffix-p ","))
-                (not (looking-at go--case-regexp))
-                (go--forward-line -1)))
-
-        (and
-         (looking-at-p go--case-regexp)
-         ;; we weren't in case list if first line ended in colon
-         ;; and the "case" line ended in colon
-         (not (and saw-colon (looking-at ".*:[[:space:]]*$"))))))))
-
-(defun go--in-composite-literal-p ()
-  "Return non-nil if point is in a composite literal."
-  (save-excursion
-    (and
-     (go-goto-opening-parenthesis)
-
-     ;; Opening paren-like character is a curly.
-     (eq (char-after) ?{)
-
-     (or
-      ;; Curly is preceded by non space (e.g. "Foo{"), definitely
-      ;; composite literal.
-      (zerop (skip-syntax-backward " "))
-
-      ;; Curly preceded by comma or semicolon. This is a composite
-      ;; literal with implicit type name.
-      (looking-back "[,:]" (1- (point)))
-
-      ;; If we made it to the beginning of line we are either a naked
-      ;; block or a composite literal with implict type name. If we
-      ;; are the latter, we must be contained in another composite
-      ;; literal.
-      (and (bolp) (go--in-composite-literal-p))))))
-
-(defun go--in-paren-with-prefix-p (paren prefix)
-  (save-excursion
-    (and
-     (go-goto-opening-parenthesis)
-     (eq (char-after) paren)
-     (skip-syntax-backward " ")
-     (> (point) (length prefix))
-     (string= prefix (buffer-substring (- (point) (length prefix)) (point))))))
-
-(defun go--in-struct-definition-p ()
-  "Return non-nil if point is inside a struct definition."
-  (go--in-paren-with-prefix-p ?{ "struct"))
-
-(defun go--in-interface-p ()
-  "Return non-nil if point is inside an interface definition."
-  (go--in-paren-with-prefix-p ?{ "interface"))
-
-
-(defun go--in-type-switch-p ()
-  "Return non-nil if point is inside a type switch statement."
-  (go--in-paren-with-prefix-p ?{ ".(type)"))
-
-(defun go--fill-prefix ()
-  "Return fill prefix for following comment paragraph."
-  (save-excursion
-    (beginning-of-line)
-
-    ;; Skip over empty lines and empty comment openers/closers.
-    (while (and
-            (or (go--empty-line-p) (go--boring-comment-p))
-            (zerop (forward-line 1))))
-
-    ;; If we are in a block comment, set prefix based on first line
-    ;; with content.
-    (if (go-in-comment-p)
-        (progn
-          (looking-at "[[:space:]]*")
-          (match-string-no-properties 0))
-
-      ;; Else if we are looking at the start of an interesting comment, our
-      ;; prefix is the comment opener and any space following.
-      (if (looking-at (concat go--comment-start-regexp "[[:space:]]*"))
-          ;; Replace "/*" opener with spaces so following lines don't
-          ;; get "/*" prefix.
-          (replace-regexp-in-string "/\\*" "  "
-                                    (match-string-no-properties 0))))))
-
-(defun go--fill-paragraph (&rest args)
-  "Wrap fill-paragraph to set custom fill-prefix."
-  (let ((fill-prefix (go--fill-prefix))
-        (fill-paragraph-function nil))
-    (apply 'fill-paragraph args)))
-
-(defun go--empty-line-p ()
-  (looking-at "[[:space:]]*$"))
-
-(defun go--boring-comment-p ()
-  "Return non-nil if we are looking at a boring comment.
-
-A boring comment is a comment with no content. For example:
-
-////////  ; boring
-// hello
-////////  ; boring
-
-/*        ; boring
-  hello
-*/        ; boring
-"
-  (or
-   (looking-at-p "[[:space:]]*//+[[:space:]]*$")
-   (looking-at-p "[[:space:]]*\\(?:/\\*+\\|\\*/+\\)[[:space:]]*$")))
-
-(defun go--interesting-comment-p ()
-  "Return non-nil if we are looking at an interesting comment.
-
-An interesting comment is one that contains content other than
-comment starter/closer characters."
-
-  (if (go-in-comment-p)
-      (and
-       (not (go--empty-line-p))
-       (not (looking-at-p "[[:space:]]*\\*/")))
-    (and
-     (looking-at go--comment-start-regexp)
-     (not (go--boring-comment-p)))))
-
-(defun go--fill-forward-paragraph (&optional arg)
-  "forward-paragraph like function used for fill-paragraph.
-
-This function is key for making fill-paragraph do the right
-thing for comments."
-  (beginning-of-line)
-  (let* ((arg (or arg 1))
-         (single (if (> arg 0) 1 -1))
-         (done nil))
-    (while (and (not done) (not (zerop arg)))
-      ;; If we are moving backwards and aren't currently looking at a
-      ;; comment, move back one line. This is to make sure
-      ;; (go--fill-forward-paragraph -1) always works properly as the
-      ;; inverse of (go--fill-forward-paragraph 1).
-      (when (and
-             (= single -1)
-             (not (go-in-comment-p))
-             (not (looking-at-p go--comment-start-regexp)))
-        (forward-line -1))
-
-      ;; Skip empty lines and comment lines with no content.
-      (while (and
-              (or (go--empty-line-p) (go--boring-comment-p))
-              (zerop (forward-line single))))
-
-      (let (saw-comment)
-        ;; Skip comment lines that have content.
-        (while (and
-                (go--interesting-comment-p)
-                (zerop (forward-line single)))
-          (setq saw-comment t))
-
-        (if (not saw-comment)
-            (progn
-              ;; In fill-region case user may have selected a region
-              ;; with non-comments. fill-region will loop forever
-              ;; until it makes it to the end of the region, so just
-              ;; fall back to `forward-paragraph' so we make progress.
-              (when mark-active
-                (setq arg (forward-paragraph arg)))
-              (setq done t))
-          ;; If we are going backwards, back up one more line so
-          ;; we are on the line before the comment.
-          (when (= single -1)
-            (forward-line 1))
-          (cl-decf arg single))))
-    arg))
-
-
-(defun go--open-paren-position ()
-  "Return non-nil if point is between '(' and ')'.
-
-The return value is the position of the opening paren."
-  (save-excursion
-    (let ((start-paren-level (go-paren-level)))
-      (and
-       (go-goto-opening-parenthesis)
-
-       ;; opening paren-like character is actually a paren
-       (eq (char-after) ?\()
-
-       ;; point is before the closing paren
-       (< (go-paren-level) start-paren-level)
-
-       (point)))))
-
-(defun go-indentation-at-point ()
-  "Return the appropriate indentation for the current line.
-
-This function works by walking a line's characters backwards. When it
-encounters a closing paren or brace it bounces to the corresponding
-opener. If it arrives at the beginning of the line you are indenting,
-it moves to the end of the previous line if the current line is a
-continuation line, else it moves to the containing opening paren or
-brace. If it arrives at the beginning of a line other than the line
-you are indenting, it will continue to the previous dangling line if
-the line you are indenting was not a continuation line, otherwise it
-is done."
-  (save-excursion
-    (beginning-of-line)
-
-    (let (
-          ;; Beginning of our starting line.
-          (start-line (point))
-
-          ;; Whether this is our first iteration of the outer while loop.
-          (first t)
-
-          ;; Whether we start in a block (i.e. our first line is not a
-          ;; continuation line and is in an "if", "for", "func" etc. block).
-          (in-block)
-
-          ;; Our desired indent relative to our ending line's indent.
-          (indent 0))
-
-      ;; Skip leading whitespace.
-      (skip-syntax-forward " ")
-
-      ;; Decrement indent if the first character on the line is a closer.
-      (when (or (eq (char-after) ?\)) (eq (char-after) ?}))
-        (cl-decf indent tab-width))
-
-      (while (or
-              ;; Always run the first iteration so we process empty lines.
-              first
-
-              ;; Otherwise stop if we are at the start of a line.
-              (not (bolp)))
-        (setq first nil)
-
-        (cl-case (char-before)
-
-          ;; We have found a closer (paren or brace).
-          ((?\) ?})
-           (backward-char)
-           (let ((bol (line-beginning-position)))
-
-             ;; Jump back to corresponding opener.
-             (go-goto-opening-parenthesis)
-
-             ;; Here we decrement the indent if we are closing an indented
-             ;; expression. In other words, the closer's line was indented
-             ;; relative to the opener's line, and that indent should not
-             ;; be inherited by our starting line.
-             (when (and
-                    ;; We care about dangling expressions, not child blocks.
-                    (not in-block)
-
-                    ;; Opener and closer aren't on same line.
-                    (< (point) bol)
-
-                    (go-previous-line-has-dangling-op-p)
-
-                    ;; Opener is at same paren level as start of line (ignore sub-expressions).
-                    (eq (go-paren-level) (save-excursion (beginning-of-line) (go-paren-level)))
-
-                    ;; This dangling line opened indent relative to previous dangling line.
-                    (go--continuation-line-indents-p))
-               (cl-decf indent tab-width))))
-
-          ;; Brackets don't affect indentation, so just skip them.
-          ((?\])
-           (backward-char)))
-
-        ;; Skip non-closers since we are only interested in closing parens/braces.
-        (skip-syntax-backward "^)" (line-beginning-position))
-
-        (when (go-in-string-or-comment-p)
-          (go-goto-beginning-of-string-or-comment))
-
-        ;; At the beginning of the starting line.
-        (when (= start-line (point))
-
-          ;; We are a continuation line.
-          (if (go-previous-line-has-dangling-op-p)
-              (progn
-                ;; Presume a continuation line always gets an extra indent.
-                ;; We reduce the indent after the loop, if necessary.
-                (cl-incf indent tab-width)
-
-                ;; Go to the end of the dangling line.
-                (goto-char (go-previous-line-has-dangling-op-p)))
-
-            ;; If we aren't a continuation line and we have an enclosing paren
-            ;; or brace, jump to opener and increment our indent.
-            (when (go-goto-opening-parenthesis)
-              ;; We started in a child block if our opener is a curly brace.
-              (setq in-block (and
-                              (eq (char-after) ?{)
-                              (looking-back "[^[:space:]][[:space:]]" (- (point) 2))))
-              (cl-incf indent tab-width))))
-
-        ;; If we started in a child block we must follow dangling lines
-        ;; until they don't dangle anymore. This is to handle cases like:
-        ;;
-        ;; if foo ||
-        ;;      foo &&
-        ;;        foo {
-        ;;   X
-        ;;
-        ;; There can be an arbitrary number of indents, so we must go back to
-        ;; the "if" to determine the indent of "X".
-        (when (and in-block (bolp) (go-previous-line-has-dangling-op-p))
-          (goto-char (go-previous-line-has-dangling-op-p))))
-
-      ;; If our ending line is a continuation line but doesn't open
-      ;; an extra indent, reduce indent. We tentatively gave indents to all
-      ;; dangling lines and all lines inside open parens, so here we take that
-      ;; indent back.
-      ;;
-      ;;                1 +                      1 +
-      ;; ending line      1 + foo(                 1 + foo(
-      ;; starting line      1,        becomes      1,
-      ;;                  )                      )
-      ;;
-      ;;
-      ;;                1 +                     1 +
-      ;; ending line      1 +         becomes     1 +
-      ;; starting line      1                     1
-      (when (and
-             (go-previous-line-has-dangling-op-p)
-             (not (go--continuation-line-indents-p)))
-        (cl-decf indent tab-width))
-
-      ;; Apply our computed indent relative to the indent of the
-      ;; ending line, or 0 if we are at the top level.
-      (if (and
-           (= 0 (go-paren-level))
-           (not (go-previous-line-has-dangling-op-p)))
-          indent
-        (+ indent (current-indentation))))))
-
-(defconst go--operator-chars "*/%<>&\\^+\\-|=!,"
-  "Individual characters that appear in operators.
-Comma is included because it is sometimes a dangling operator, so
-needs to be considered by `go--continuation-line-indents-p'")
-
-(defun go--operator-precedence (op)
-  "Go operator precedence (higher binds tighter).
-
-Comma gets the default 0 precedence which is appropriate because commas
-are loose binding expression separators."
-  (cl-case (intern op)
-    (! 6)
-    ((* / % << >> & &^) 5)
-    ((+ - | ^) 4)
-    ((== != < <= > >=) 3)
-    (&& 2)
-    (|| 1)
-    (t 0)))
-
-(defun go--continuation-line-indents-p ()
-  "Return non-nil if the current continuation line opens an additional indent.
-
-This function works by looking at the Go operators used on the current
-line. If all the operators bind tighter than the previous line's
-dangling operator and the current line ends in a dangling operator or
-open paren, the next line will have an additional indent.
-
-For example:
-foo ||
-  foo && // this continuation line opens another indent
-    foo
-"
-  (save-excursion
-    (let (prev-op (all-tighter t))
-
-      ;; Record the dangling operator from previous line.
-      (save-excursion
-        (goto-char (go-previous-line-has-dangling-op-p))
-        (go--end-of-line)
-        (skip-syntax-backward " ")
-        (let ((end (point)))
-          (skip-chars-backward go--operator-chars)
-          (setq prev-op (buffer-substring-no-properties (point) end))))
-
-      (beginning-of-line)
-
-      (when (or
-             ;; We can only open indent if we have a dangling operator, or
-             (go--current-line-has-dangling-op-p)
-             ;; we end in an opening paren/brace or comma.
-             (go--line-suffix-p "[(,]\\|[^[:space:]]{"))
-
-        (let ((prev-precedence (go--operator-precedence prev-op))
-              (start-depth (go-paren-level))
-              (line-start (line-beginning-position)))
-
-          (end-of-line)
-
-          ;; While we haven't found a looser operator and are on the starting line...
-          (while (and all-tighter (> (point) line-start))
-
-            ;; Skip over non-operator characters.
-            (skip-chars-backward (concat "^" go--operator-chars) line-start)
-
-            (let ((end (point)))
-              (cond
-               ;; Ignore sub-expressions at different paren levels.
-               ((/= (go-paren-level) start-depth)
-                (skip-syntax-backward "^()"))
-
-               ((go-in-string-or-comment-p)
-                (go-goto-beginning-of-string-or-comment))
-
-               ;; We found an operator. Check if it has lower precedence.
-               ((/= (skip-chars-backward go--operator-chars) 0)
-                (when (>=
-                       prev-precedence
-                       (go--operator-precedence (buffer-substring (point) end)))
-                  (setq all-tighter nil)))))))
-        all-tighter))))
-
-(defun go--end-of-line ()
-  "Move to the end of the code on the current line.
-Point will be left before any trailing comments. Point will be left
-after the opening backtick of multiline strings."
-  (end-of-line)
-  (let ((keep-going t))
-    (while keep-going
-      (skip-syntax-backward " ")
-      (when (looking-back "\\*/" (- (point) 2))
-        ;; back up so we are in the /* comment */
-        (backward-char))
-      (if (go-in-comment-p)
-          (go-goto-beginning-of-string-or-comment)
-        (setq keep-going nil))))
-  (when (go-in-string-p)
-    (go-goto-beginning-of-string-or-comment)
-    ;; forward one so point is after the opening "`"
-    (forward-char)))
-
-(defun go--line-suffix-p (re)
-  "Return non-nil if RE matches the end of the line starting from `point'.
-
-Trailing whitespace, trailing comments and trailing multiline strings are
-ignored."
-  (let ((start (point))
-        (end (save-excursion (go--end-of-line) (point))))
-    (when (< start end)
-      (string-match-p
-       (concat "\\(?:" re "\\)$")
-       (buffer-substring-no-properties start end)))))
-
-(defun go--boring-line-p ()
-  "Return non-nil if the current line probably doesn't impact indentation.
-
-A boring line is one that starts with a comment, is empty, is part of a
-multiline comment, or starts and ends in a multiline string."
-  (or
-   (looking-at (concat go--comment-start-regexp "\\|[[:space:]]*$"))
-   (go-in-comment-p)
-   (and (go-in-string-p) (save-excursion (end-of-line) (go-in-string-p)))))
-
-(defun go--forward-line (&optional count)
-  "Like `forward-line' but skip comments and empty lines.
-
-Return non-nil if point changed lines."
-  (let (moved)
-    (while (and
-            (zerop (forward-line count))
-            (setq moved t)
-            (go--boring-line-p))
-      (setq count (if (and count (< count 0 )) -1 1)))
-    moved))
-
-(defconst go--comment-start-regexp "[[:space:]]*\\(?:/[/*]\\)")
-
-(defun go--case-comment-p (indent)
-  "Return non-nil if looking at a comment attached to a case statement.
-
-INDENT is the normal indent of this line, i.e. that of the case body."
-  (when (and
-         (> (current-indentation) 0)
-         (looking-at go--comment-start-regexp))
-
-    (let (switch-before
-          case-after
-          has-case-aligned-preceding-comment)
-
-      (save-excursion
-        ;; Search for previous case-aligned comment.
-        (while (and
-                (zerop (forward-line -1))
-                (cond
-                 ((looking-at "^[[:space:]]*$"))
-
-                 ((looking-at go--comment-start-regexp)
-                  (when (= (current-indentation) (- indent tab-width))
-                    (setq has-case-aligned-preceding-comment t))
-                  t)
-
-                 ((go-in-comment-p)))))
-
-        ;; Record if a switch (or select) precedes us.
-        (setq switch-before (looking-at "^[[:space:]]*\\(switch\\|select\\)[[:space:]]")))
-
-      ;; Record if first proceeding non-comment line is a case statement.
-      (save-excursion
-        (while (and
-                (zerop (forward-line 1))
-                (or
-                 (looking-at go--comment-start-regexp)
-                 (looking-at "^[[:space:]]*$")
-                 (go-in-comment-p))))
-
-        (setq case-after (looking-at go--case-or-default-regexp)))
-
-      (and
-       ;; a "case" statement comes after our comment
-       case-after
-
-       (or
-        ;; "switch" statement precedes us, always align with "case"
-        switch-before
-
-        ;; a preceding comment is aligned with "case", we should too
-        has-case-aligned-preceding-comment
-
-        ;; other cases are ambiguous, so if comment is currently
-        ;; aligned with "case", leave it that way
-        (= (current-indentation) (- indent tab-width)))))))
-
-(defconst go--case-regexp "\\([[:space:]]*case\\([[:space:]]\\|$\\)\\)")
-(defconst go--case-or-default-regexp (concat "\\(" go--case-regexp "\\|"  "[[:space:]]*default:\\)"))
-
-(defun go-mode-indent-line ()
-  (interactive)
-  (let (indent
-        shift-amt
-        ;; case sensitively match "case", "default", etc.
-        (case-fold-search nil)
-        (pos (- (point-max) (point)))
-        (point (point))
-        (beg (line-beginning-position)))
-    (back-to-indentation)
-    (if (go-in-string-or-comment-p)
-        (goto-char point)
-      (setq indent (go-indentation-at-point))
-      (when (or
-             (and
-              (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|" go--case-or-default-regexp))
-              ;; don't think last part of multiline case statement is a label
-              (not (go-previous-line-has-dangling-op-p))
-              (not (go--in-case-clause-list-p))
-              (not (go--in-composite-literal-p)))
-
-             ;; comment attached above a "case" statement
-             (go--case-comment-p indent))
-        (cl-decf indent tab-width))
-      (setq shift-amt (- indent (current-column)))
-      (if (zerop shift-amt)
-          nil
-        (delete-region beg (point))
-        (indent-to indent))
-      ;; If initial point was within line's indentation,
-      ;; position after the indentation.  Else stay at same point in text.
-      (if (> (- (point-max) pos) (point))
-          (goto-char (- (point-max) pos))))))
-
-(defun go-beginning-of-defun (&optional count)
-  (unless (bolp)
-    (end-of-line))
-  (setq count (or count 1))
-  (let (first failure)
-    (dotimes (i (abs count))
-      (setq first t)
-      (while (and (not failure)
-                  (or first (go-in-string-or-comment-p)))
-        (if (>= count 0)
-            (progn
-              (go--backward-irrelevant)
-              (if (not (re-search-backward go-func-meth-regexp nil t))
-                  (setq failure t)))
-          (if (looking-at go-func-meth-regexp)
-              (forward-char))
-          (if (not (re-search-forward go-func-meth-regexp nil t))
-              (setq failure t)))
-        (setq first nil)))
-    (if (< count 0)
-        (beginning-of-line))
-    (not failure)))
-
-(defun go-end-of-defun ()
-  (let (orig-level)
-    ;; It can happen that we're not placed before a function by emacs
-    (if (not (looking-at "func"))
-        (go-beginning-of-defun -1))
-    ;; Find the { that starts the function, i.e., the next { that isn't
-    ;; preceded by struct or interface, or a comment or struct tag.  BUG:
-    ;; breaks if there's a comment between the struct/interface keyword and
-    ;; bracket, like this:
-    ;;
-    ;;     struct /* why? */ {
-    (while (progn
-      (skip-chars-forward "^{")
-      (forward-char)
-      (or (go-in-string-or-comment-p)
-          (looking-back "\\(struct\\|interface\\)\\s-*{"
-                        (line-beginning-position)))))
-    (setq orig-level (go-paren-level))
-    (while (>= (go-paren-level) orig-level)
-      (skip-chars-forward "^}")
-      (forward-char))))
-
-
-(defvar go--fontify-param-has-name nil
-  "Whether the current params list has names.
-
-This is used during fontification of function signatures.")
-
-(defun go--fontify-param-pre ()
-  "Set `go--fontify-param-has-name' appropriately.
-
-This is used as an anchored font lock keyword PRE-MATCH-FORM. We
-must set `go--fontify-param-has-name' ahead of time because you
-can't know if the param list is types only or names and types
-until you see the end. For example:
-
-// types only
-func foo(int, string) {}
-
-// names and types (don't know so until you see the \"int\").
-func foo(i, j int) {}
-"
-  (setq go--fontify-param-has-name (eq
-                                    (go--parameter-list-type (point-max))
-                                    'present))
-  ;; Return position of closing paren so we process the entire
-  ;; multiline param list.
-  (save-excursion
-    (let ((depth (go-paren-level)))
-      (while (and
-              (re-search-forward ")" nil t)
-              (> (go-paren-level) depth))))
-    (point)))
-
-(defun go--match-param-start (end)
-  "Search for the starting of param lists.
-
-Search for the opening `(' of function signature param lists.
-This covers the func receiver, params, and results. Interface
-declarations are also included."
-  (let (found-match)
-    (while (and
-            (not found-match)
-            (re-search-forward (concat "\\(\\_<" go-identifier-regexp "\\)?(") end t))
-      (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))))))))
-    found-match))
-
-
-(defconst go--named-param-re
-  (concat "[[:space:]\n]*\\(" go-identifier-regexp "\\)\\(?:[[:space:]]+\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)]\\)?")
-  "Regexp to match named param such as \"s *string\" in:
-
-func(i int, s *string) { }")
-
-(defconst go--unnamed-param-re
-  (concat "\\(\\)[[:space:]\n]*\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)]")
-  "Regexp to match unnamed param such as \"*string\" in:
-
-func(int, *string) { }
-
-We start with an empty subexp since our font lock keyword expects
-subexp 1 to a variable name, but we have no variable.")
-
-(defun go--fontify-param (end)
-  "Match a param within a param list.
-
-Our parent font lock matcher is anchored to the beginning of the
-param list. `go--fontify-param-has-name' has been set
-appropriately. We match the next param and advance point to after
-the next comma or to the closing paren."
-  (let (found-match done)
-    ;; We loop until match because there are some params that we can't
-    ;; handle (but we may need to handle subsequent params). For
-    ;; example:
-    ;;
-    ;; // We don't handle the interface, so we must skip it and handle
-    ;; // "string".
-    ;; func(int, interface { foo() }, string)
-    (while (and (not found-match) (not done))
-      (if go--fontify-param-has-name
-          (when (looking-at go--named-param-re)
-            (setq found-match t))
-        (when (looking-at go--unnamed-param-re)
-          (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))))
-    found-match))
-
-(defun go--search-next-comma (end)
-  "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 "[^,)]")
-                    (> (go-paren-level) orig-level)))
-      (forward-char))
-    (when (and (looking-at-p ",")
-               (< (point) (1- end)))
-      (forward-char)
-      t)))
-
-(defun go--looking-at-keyword ()
-  (and (looking-at (concat "\\(" go-identifier-regexp "\\)"))
-       (member (match-string 1) go-mode-keywords)))
-
-(defun go--match-type-switch-case (end)
-  "Match a \"case\" clause within a type switch."
-  (let (found-match)
-    (while (and
-            (not found-match)
-
-            ;; Search for "case" statements.
-            (re-search-forward "^[[:space:]]*case " end t))
-
-      ;; Make sure we are in a type switch statement.
-      (setq found-match (go--in-type-switch-p)))
-    found-match))
-
-(defun go--fontify-type-switch-case (end)
-  "Match a single type within a type switch case."
-  (let (found-match done)
-    ;; Loop until we find a match because we must skip types we don't
-    ;; handle, such as "interface { foo() }".
-    (while (and (not found-match) (not done))
-      (when (looking-at (concat "[[:space:]\n]*" go-type-name-regexp "[[:space:]]*[,:]"))
-        (goto-char (match-end 1))
-        (setq found-match t))
-      (setq done (not (go--search-next-comma end))))
-    found-match))
-
-(defun go--in-var-decl-p ()
-  "Return non-nil if insde a \"var\" decl."
-  (or
-   (go--in-paren-with-prefix-p ?\( "var")
-   (save-excursion
-     (let ((depth (go-paren-level)))
-       (beginning-of-line)
-       (and
-        (= (go-paren-level) depth)
-        (looking-at-p "[[:space:]]*var[[:space:]]"))))))
-
-(defconst go--match-var-re (concat "\\(?:^\\|[[:space:]]\\)\\(" go-identifier-regexp "\\)\\_>"))
-
-(defun go--match-var-decl (end)
-  "Match variable declarations inside \"var\" decls and \":=\"
-  assignments."
-  (let (found-match)
-    (while (and
-            (not found-match)
-            (re-search-forward go--match-var-re end t))
-
-      (save-match-data
-        (save-excursion
-          (cond
-         ((member (match-string 1) go-mode-keywords))
-
-         ((and
-           ;; We are inside a "var" decl.
-           (go--in-var-decl-p)
-
-           (or
-            ;; We are followed directly by comma and aren't on right
-            ;; side of equals sign.
-            (and
-             (looking-at "[[:space:]]*,")
-             (not (looking-back "=.*" (line-beginning-position))))
-
-            ;; Or we are followed by a space and non-space (non-space
-            ;; might be a type name or "=").
-            (looking-at "[[:space:]]+[^[:space:]]")))
-          (setq found-match t))
-
-         ;; Left side of ":=" assignmnet.
-         ((looking-at ".*:=")
-          (let ((depth (go-paren-level)))
-            (goto-char (match-end 0))
-            ;; Make sure the ":=" isn't in a comment or a sub-block.
-            (setq found-match (and
-                               (not (go-in-string-or-comment-p))
-                               (= depth (go-paren-level))))))))))
-    found-match))
-
-(defconst go--ident-type-pair-re (concat "\\_<\\(" go-identifier-regexp "\\)[[:space:]]+" go-type-name-regexp))
-
-(defun go--match-ident-type-pair (end)
-  "Search for identifier + type-name pairs.
-
-For example, this looks for the \"foo bar\" in \"var foo bar\",
-yielding match-data for \"bar\" since that is a type name to be
-fontified. This approach matches type names in var and const
-decls, and in struct definitions. Return non-nil if search
-succeeds."
-  (let (found-match)
-    (while (and
-            (not found-match)
-            (re-search-forward go--ident-type-pair-re end t))
-
-      ;; Make sure the neither match is a keyword.
-      (if (member (match-string 2) go-mode-keywords)
-          (goto-char (match-end 2))
-        (if (member (match-string 1) go-mode-keywords)
-            (goto-char (match-end 1))
-          (setq found-match t))))
-
-    found-match))
-
-
-(defun go--parameter-list-type (end)
-  "Return `present' if the parameter list has names, or `absent' if not.
-Assumes 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))))
-
-(defun go--reset-dangling-cache-before-change (&optional _beg _end)
-  "Reset `go-dangling-cache'.
-
-This is intended to be called from `before-change-functions'."
-  (setq go-dangling-cache (make-hash-table :test 'eql)))
-
-(defun go--electric-indent-function (inserted-char)
-  (cond
-   ;; Indent after starting a "//" or "/*" comment.
-   ;; This is handy for comments above "case" statements.
-   ((or (eq inserted-char ?/) (eq inserted-char ?*))
-    (when (eq (char-before (1- (point))) ?/)
-      'do-indent))
-
-   ((eq inserted-char ? )
-    (and
-     (eq (char-before (- (point) 1)) ?e)
-     (eq (char-before (- (point) 2)) ?s)
-     (eq (char-before (- (point) 3)) ?a)
-     (eq (char-before (- (point) 4)) ?c)))))
-
-;;;###autoload
-(define-derived-mode go-mode prog-mode "Go"
-  "Major mode for editing Go source text.
-
-This mode provides (not just) basic editing capabilities for
-working with Go code. It offers almost complete syntax
-highlighting, indentation that is almost identical to gofmt and
-proper parsing of the buffer content to allow features such as
-navigation by function, manipulation of comments or detection of
-strings.
-
-In addition to these core features, it offers various features to
-help with writing Go code. You can directly run buffer content
-through gofmt, read godoc documentation from within Emacs, modify
-and clean up the list of package imports or interact with the
-Playground (uploading and downloading pastes).
-
-The following extra functions are defined:
-
-- `gofmt'
-- `godoc' and `godoc-at-point'
-- `go-import-add'
-- `go-remove-unused-imports'
-- `go-goto-arguments'
-- `go-goto-docstring'
-- `go-goto-function'
-- `go-goto-function-name'
-- `go-goto-imports'
-- `go-goto-return-values'
-- `go-goto-method-receiver'
-- `go-play-buffer' and `go-play-region'
-- `go-download-play'
-- `godef-describe' and `godef-jump'
-- `go-coverage'
-- `go-set-project'
-- `go-reset-gopath'
-
-If you want to automatically run `gofmt' before saving a file,
-add the following hook to your emacs configuration:
-
-\(add-hook 'before-save-hook #'gofmt-before-save)
-
-If you want to use `godef-jump' instead of etags (or similar),
-consider binding godef-jump to `M-.', which is the default key
-for `find-tag':
-
-\(add-hook 'go-mode-hook (lambda ()
-                          (local-set-key (kbd \"M-.\") #'godef-jump)))
-
-Please note that godef is an external dependency. You can install
-it with
-
-go get github.com/rogpeppe/godef
-
-
-If you're looking for even more integration with Go, namely
-on-the-fly syntax checking, auto-completion and snippets, it is
-recommended that you look at flycheck
-\(see URL `https://github.com/flycheck/flycheck') or flymake in combination
-with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode
-\(see URL `https://github.com/nsf/gocode'), go-eldoc
-\(see URL `github.com/syohex/emacs-go-eldoc') and yasnippet-go
-\(see URL `https://github.com/dominikh/yasnippet-go')"
-
-  ;; Font lock
-  (setq font-lock-defaults '(go--build-font-lock-keywords))
-  (setq font-lock-multiline t)
-
-  ;; Indentation
-  (set (make-local-variable 'indent-line-function) #'go-mode-indent-line)
-
-  ;; Comments
-  (set (make-local-variable 'comment-start) "// ")
-  (set (make-local-variable 'comment-end)   "")
-  (set (make-local-variable 'comment-use-syntax) t)
-  (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
-
-  (set (make-local-variable 'beginning-of-defun-function) #'go-beginning-of-defun)
-  (set (make-local-variable 'end-of-defun-function) #'go-end-of-defun)
-
-  (set (make-local-variable 'parse-sexp-lookup-properties) t)
-  (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax)
-
-  (when (boundp 'electric-indent-chars)
-    (set (make-local-variable 'electric-indent-chars) '(?\n ?} ?\) ?:))
-    (add-hook 'electric-indent-functions #'go--electric-indent-function nil t))
-
-  (set (make-local-variable 'compilation-error-screen-columns) nil)
-
-  (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
-  (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t)
-
-  (set (make-local-variable 'adaptive-fill-function) #'go--fill-prefix)
-  (set (make-local-variable 'fill-paragraph-function) #'go--fill-paragraph)
-  (set (make-local-variable 'fill-forward-paragraph-function) #'go--fill-forward-paragraph)
-
-  ;; ff-find-other-file
-  (setq ff-other-file-alist 'go-other-file-alist)
-
-  (setq imenu-generic-expression
-        '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
-          ("func" "^func *\\(.*\\) {" 1)))
-  (imenu-add-to-menubar "Index")
-
-  ;; Go style
-  (setq indent-tabs-mode t)
-
-  ;; Handle unit test failure output in compilation-mode
-  ;;
-  ;; Note that we add our entry to the beginning of
-  ;; compilation-error-regexp-alist. In older versions of Emacs, the
-  ;; list was processed from the end, and we would've wanted to add
-  ;; ours last. But at some point this changed, and now the list is
-  ;; processed from the beginning. It's important that our entry comes
-  ;; before gnu, because gnu matches go test output, but includes the
-  ;; leading whitespace in the file name.
-  ;;
-  ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html
-  ;; documents the old, reverseed order.
-  (when (and (boundp 'compilation-error-regexp-alist)
-             (boundp 'compilation-error-regexp-alist-alist))
-    (add-to-list 'compilation-error-regexp-alist 'go-test)
-    (add-to-list 'compilation-error-regexp-alist-alist
-                 '(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
-
-;;;###autoload
-(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
-
-(defun go--apply-rcs-patch (patch-buffer)
-  "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."
-  (let ((target-buffer (current-buffer))
-        ;; Relative offset between buffer line numbers and line numbers
-        ;; in patch.
-        ;;
-        ;; Line numbers in the patch are based on the source file, so
-        ;; we have to keep an offset when making changes to the
-        ;; buffer.
-        ;;
-        ;; Appending lines decrements the offset (possibly making it
-        ;; negative), deleting lines increments it. This order
-        ;; simplifies the forward-line invocations.
-        (line-offset 0)
-        (column (current-column)))
-    (save-excursion
-      (with-current-buffer patch-buffer
-        (goto-char (point-min))
-        (while (not (eobp))
-          (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
-            (error "Invalid rcs patch or internal error in go--apply-rcs-patch"))
-          (forward-line)
-          (let ((action (match-string 1))
-                (from (string-to-number (match-string 2)))
-                (len  (string-to-number (match-string 3))))
-            (cond
-             ((equal action "a")
-              (let ((start (point)))
-                (forward-line len)
-                (let ((text (buffer-substring start (point))))
-                  (with-current-buffer target-buffer
-                    (cl-decf line-offset len)
-                    (goto-char (point-min))
-                    (forward-line (- from len line-offset))
-                    (insert text)))))
-             ((equal action "d")
-              (with-current-buffer target-buffer
-                (go--goto-line (- from line-offset))
-                (cl-incf line-offset len)
-                (go--delete-whole-line len)))
-             (t
-              (error "Invalid rcs patch or internal error in go--apply-rcs-patch")))))))
-    (move-to-column column)))
-
-(defun gofmt--is-goimports-p ()
-  (string-equal (file-name-base gofmt-command) "goimports"))
-
-(defun gofmt ()
-  "Format the current buffer according to the formatting tool.
-
-The tool used can be set via ‘gofmt-command’ (default: gofmt) and additional
-arguments can be set as a list via ‘gofmt-args’."
-  (interactive)
-  (let ((tmpfile (go--make-nearby-temp-file "gofmt" nil ".go"))
-        (patchbuf (get-buffer-create "*Gofmt patch*"))
-        (errbuf (if gofmt-show-errors (get-buffer-create "*Gofmt Errors*")))
-        (coding-system-for-read 'utf-8)
-        (coding-system-for-write 'utf-8)
-        our-gofmt-args)
-
-    (unwind-protect
-        (save-restriction
-          (widen)
-          (if errbuf
-              (with-current-buffer errbuf
-                (setq buffer-read-only nil)
-                (erase-buffer)))
-          (with-current-buffer patchbuf
-            (erase-buffer))
-
-          (write-region nil nil tmpfile)
-
-          (when (and (gofmt--is-goimports-p) buffer-file-name)
-            (setq our-gofmt-args
-                  (append our-gofmt-args
-                          ;; srcdir, despite its name, supports
-                          ;; accepting a full path, and some features
-                          ;; of goimports rely on knowing the full
-                          ;; name.
-                          (list "-srcdir" (go--file-local-name
-                                           (file-truename buffer-file-name))))))
-          (setq our-gofmt-args
-                (append our-gofmt-args gofmt-args
-                        (list "-w" (go--file-local-name tmpfile))))
-          (message "Calling gofmt: %s %s" gofmt-command our-gofmt-args)
-          ;; We're using errbuf for the mixed stdout and stderr output. This
-          ;; is not an issue because gofmt -w does not produce any stdout
-          ;; output in case of success.
-          (if (zerop (apply #'process-file gofmt-command nil errbuf nil our-gofmt-args))
-              (progn
-                ;; There is no remote variant of ‘call-process-region’, but we
-                ;; can invoke diff locally, and the results should be the same.
-                (if (zerop (let ((local-copy (file-local-copy tmpfile)))
-                             (unwind-protect
-                                 (call-process-region
-                                  (point-min) (point-max) "diff" nil patchbuf
-                                  nil "-n" "-" (or local-copy tmpfile))
-                               (when local-copy (delete-file local-copy)))))
-                    (message "Buffer is already gofmted")
-                  (go--apply-rcs-patch patchbuf)
-                  (message "Applied gofmt"))
-                (if errbuf (gofmt--kill-error-buffer errbuf)))
-            (message "Could not apply gofmt")
-            (if errbuf (gofmt--process-errors (buffer-file-name) tmpfile errbuf))))
-
-      (kill-buffer patchbuf)
-      (delete-file tmpfile))))
-
-
-(defun gofmt--process-errors (filename tmpfile errbuf)
-  (with-current-buffer errbuf
-    (if (eq gofmt-show-errors 'echo)
-        (progn
-          (message "%s" (buffer-string))
-          (gofmt--kill-error-buffer errbuf))
-      ;; Convert the gofmt stderr to something understood by the compilation mode.
-      (goto-char (point-min))
-      (if (save-excursion
-            (save-match-data
-              (search-forward "flag provided but not defined: -srcdir" nil t)))
-          (insert "Your version of goimports is too old and doesn't support vendoring. Please update goimports!\n\n"))
-      (insert "gofmt errors:\n")
-      (let ((truefile
-             (if (gofmt--is-goimports-p)
-                 (concat (file-name-directory filename) (file-name-nondirectory tmpfile))
-               tmpfile)))
-        (while (search-forward-regexp
-                (concat "^\\(" (regexp-quote (go--file-local-name truefile))
-                        "\\):")
-                nil t)
-          (replace-match (file-name-nondirectory filename) t t nil 1)))
-      (compilation-mode)
-      (display-buffer errbuf))))
-
-(defun gofmt--kill-error-buffer (errbuf)
-  (let ((win (get-buffer-window errbuf)))
-    (if win
-        (quit-window t win)
-      (kill-buffer errbuf))))
-
-;;;###autoload
-(defun gofmt-before-save ()
-  "Add this to .emacs to run gofmt on the current buffer when saving:
-\(add-hook 'before-save-hook 'gofmt-before-save).
-
-Note that this will cause ‘go-mode’ to get loaded the first time
-you save any file, kind of defeating the point of autoloading."
-
-  (interactive)
-  (when (eq major-mode 'go-mode) (gofmt)))
-
-(defun godoc--read-query ()
-  "Read a godoc query from the minibuffer."
-  (if godoc-use-completing-read
-      (completing-read "godoc; "
-                       (go-packages) nil nil nil 'go-godoc-history)
-    (read-from-minibuffer "godoc: " nil nil nil 'go-godoc-history)))
-
-(defun godoc--buffer-name (query)
-  "Determine the name to use for the output buffer of a given godoc QUERY."
-  (if godoc-reuse-buffer
-      "*godoc*"
-    (concat "*godoc " query "*")))
-
-(defun godoc--get-buffer (query)
-  "Get an empty buffer for a godoc QUERY."
-  (let* ((buffer-name (godoc--buffer-name query))
-         (buffer (get-buffer buffer-name)))
-    ;; Kill the existing buffer if it already exists.
-    (when buffer (kill-buffer buffer))
-    (get-buffer-create buffer-name)))
-
-(defun godoc--buffer-sentinel (proc event)
-  "Sentinel function run when godoc command completes."
-  (with-current-buffer (process-buffer proc)
-    (cond ((string= event "finished\n")  ;; Successful exit.
-           (goto-char (point-min))
-           (godoc-mode)
-           (display-buffer (current-buffer) t))
-          ((/= (process-exit-status proc) 0)  ;; Error exit.
-           (let ((output (buffer-string)))
-             (kill-buffer (current-buffer))
-             (message (concat "godoc: " output)))))))
-
-(define-derived-mode godoc-mode special-mode "Godoc"
-  "Major mode for showing Go documentation."
-  (view-mode-enter))
-
-;;;###autoload
-(defun godoc (query)
-  "Show Go documentation for QUERY, much like \\<go-mode-map>\\[man]."
-  (interactive (list (godoc--read-query)))
-  (go--godoc query godoc-command))
-
-(defun go--godoc (query command)
-  (unless (string= query "")
-    (set-process-sentinel
-     (start-process-shell-command "godoc" (godoc--get-buffer query)
-                                  (concat command " " query))
-     'godoc--buffer-sentinel)
-    nil))
-
-(defun godoc-at-point (point)
-  "Show Go documentation for the identifier at POINT.
-
-It uses `godoc-at-point-function' to look up the documentation."
-  (interactive "d")
-  (funcall godoc-at-point-function point))
-
-(defun go-goto-imports ()
-  "Move point to the block of imports.
-
-If using
-
-  import (
-    \"foo\"
-    \"bar\"
-  )
-
-it will move point directly behind the last import.
-
-If using
-
-  import \"foo\"
-  import \"bar\"
-
-it will move point to the next line after the last import.
-
-If no imports can be found, point will be moved after the package
-declaration."
-  (interactive)
-  ;; FIXME if there's a block-commented import before the real
-  ;; imports, we'll jump to that one.
-
-  ;; Generally, this function isn't very forgiving. it'll bark on
-  ;; extra whitespace. It works well for clean code.
-  (let ((old-point (point)))
-    (goto-char (point-min))
-    (cond
-     ((re-search-forward "^import ()" nil t)
-      (backward-char 1)
-      'block-empty)
-     ((re-search-forward "^import ([^)]+)" nil t)
-      (backward-char 2)
-      'block)
-     ((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t)
-      'single)
-     ((re-search-forward "^[[:space:]\n]*package .+?\n" nil t)
-      (message "No imports found, moving point after package declaration")
-      'none)
-     (t
-      (goto-char old-point)
-      (message "No imports or package declaration found. Is this really a Go file?")
-      'fail))))
-
-(defun go-play-buffer ()
-  "Like `go-play-region', but acts on the entire buffer."
-  (interactive)
-  (go-play-region (point-min) (point-max)))
-
-(defun go-play-region (start end)
-  "Send the region between START and END to the Playground.
-If non-nil `go-play-browse-function' is called with the
-Playground URL.
-
-By default this function will prompt to confirm you want to upload
-code to the Playground. You can disable the confirmation by setting
-`go-confirm-playground-uploads' to nil.
-"
-  (interactive "r")
-  (if (and go-confirm-playground-uploads
-           (not (yes-or-no-p "Upload to public Go Playground?")))
-      (message "Upload aborted")
-    (let* ((url-request-method "POST")
-           (url-request-extra-headers
-            '(("Content-Type" . "application/x-www-form-urlencoded")))
-           (url-request-data
-            (encode-coding-string
-             (buffer-substring-no-properties start end)
-             'utf-8))
-
-           (content-buf (url-retrieve
-                         "https://play.golang.org/share"
-                         (lambda (arg)
-                           (cond
-                            ((equal :error (car arg))
-                             (signal 'go-play-error (cdr arg)))
-                            (t
-                             (re-search-forward "\n\n")
-                             (let ((url (format "https://play.golang.org/p/%s"
-                                                (buffer-substring (point) (point-max)))))
-                               (when go-play-browse-function
-                                 (funcall go-play-browse-function url))))))))))))
-
-;;;###autoload
-(defun go-download-play (url)
-  "Download a paste from the playground and insert it in a Go buffer.
-Tries to look for a URL at point."
-  (interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap-string-at-point 'url)))))
-  (with-current-buffer
-      (let ((url-request-method "GET") url-request-data url-request-extra-headers)
-        (url-retrieve-synchronously (concat url ".go")))
-    (let ((buffer (generate-new-buffer (concat (car (last (split-string url "/"))) ".go"))))
-      (goto-char (point-min))
-      (re-search-forward "\n\n")
-      (copy-to-buffer buffer (point) (point-max))
-      (kill-buffer)
-      (with-current-buffer buffer
-        (go-mode)
-        (switch-to-buffer buffer)))))
-
-(defun go-propertize-syntax (start end)
-  (save-excursion
-    (goto-char start)
-    (while (search-forward "\\" end t)
-      (put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9))))))
-
-(defun go-import-add (arg import)
-  "Add a new IMPORT to the list of imports.
-
-When called with a prefix ARG asks for an alternative name to
-import the package as.
-
-If no list exists yet, one will be created if possible.
-
-If an identical import has been commented, it will be
-uncommented, otherwise a new import will be added."
-
-  ;; - If there's a matching `// import "foo"`, uncomment it
-  ;; - If we're in an import() block and there's a matching `"foo"`, uncomment it
-  ;; - Otherwise add a new import, with the appropriate syntax
-  (interactive
-   (list
-    current-prefix-arg
-    (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go-packages)))))
-  (save-excursion
-    (let (as line import-start)
-      (if arg
-          (setq as (read-from-minibuffer "Import as: ")))
-      (if as
-          (setq line (format "%s \"%s\"" as import))
-        (setq line (format "\"%s\"" import)))
-
-      (goto-char (point-min))
-      (if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line "$") nil t)
-          (uncomment-region (line-beginning-position) (line-end-position))
-        (cl-case (go-goto-imports)
-          ('fail (message "Could not find a place to add import."))
-          ('block-empty
-           (insert "\n\t" line "\n"))
-          ('block
-              (save-excursion
-                (re-search-backward "^import (")
-                (setq import-start (point)))
-            (if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line "$")  import-start t)
-                (uncomment-region (line-beginning-position) (line-end-position))
-              (insert "\n\t" line)))
-          ('single (insert "import " line "\n"))
-          ('none (insert "\nimport (\n\t" line "\n)\n")))))))
-
-(defun go-root-and-paths ()
-  (let* ((output (process-lines go-command "env" "GOROOT" "GOPATH"))
-         (root (car output))
-         (paths (split-string (cadr output) path-separator)))
-    (cons root paths)))
-
-(defun go--string-prefix-p (s1 s2 &optional ignore-case)
-  "Return non-nil if S1 is a prefix of S2.
-If IGNORE-CASE is non-nil, the comparison is case-insensitive."
-  (eq t (compare-strings s1 nil nil
-                         s2 0 (length s1) ignore-case)))
-
-(defun go--directory-dirs (dir)
-  "Recursively return all subdirectories in DIR."
-  (if (file-directory-p dir)
-      (let ((dir (directory-file-name dir))
-            (dirs '())
-            (files (directory-files dir nil nil t)))
-        (dolist (file files)
-          (unless (member file '("." ".."))
-            (let ((file (concat dir "/" file)))
-              (if (and (file-directory-p file)
-                       (not (file-symlink-p file)))
-                  (setq dirs (append (cons file
-                                           (go--directory-dirs file))
-                                     dirs))))))
-        dirs)
-    '()))
-
-
-(defun go-packages ()
-  (funcall go-packages-function))
-
-(defun go-packages-native ()
-  "Return a list of all installed Go packages. Obsolete.
-It looks for archive files in /pkg/. This strategy does not work
-well with the Go build cache or Go modules.
-
-You should use `go-packages-go-list' instead."
-  (sort
-   (delete-dups
-    (cl-mapcan
-     (lambda (topdir)
-       (let ((pkgdir (concat topdir "/pkg/")))
-         (cl-mapcan (lambda (dir)
-                   (mapcar (lambda (file)
-                             (let ((sub (substring file (length pkgdir) -2)))
-                               (unless (or (go--string-prefix-p "obj/" sub) (go--string-prefix-p "tool/" sub))
-                                 (mapconcat #'identity (cdr (split-string sub "/")) "/"))))
-                           (if (file-directory-p dir)
-                               (directory-files dir t "\\.a$"))))
-                 (if (file-directory-p pkgdir)
-                     (go--directory-dirs pkgdir)))))
-     (go-root-and-paths)))
-   #'string<))
-
-(defun go-packages-go-list ()
-  "Return a list of all Go packages, using `go list'."
-  (process-lines go-command "list" "-e" "all"))
-
-(defun go-unused-imports-lines ()
-  (reverse (remove nil
-                   (mapcar
-                    (lambda (line)
-                      (when (string-match "^\\(.+\\):\\([[:digit:]]+\\):\\([[:digit:]]+\\): imported and not used: \".+\".*$" line)
-                        (let ((error-file-name (match-string 1 line))
-                              (error-line-num (match-string 2 line)))
-                          (if (string= (file-truename error-file-name) (file-truename buffer-file-name))
-                              (string-to-number error-line-num)))))
-                    (split-string (shell-command-to-string
-                                   (concat go-command
-                                           (if (string-match "_test\\.go$" buffer-file-truename)
-                                               " test -c"
-                                             (concat " build -o " null-device))
-                                           " -gcflags=-e"
-                                           " "
-                                           (shell-quote-argument (file-truename buffer-file-name)))) "\n")))))
-
-(defun go-remove-unused-imports (arg)
-  "Remove all unused imports.
-If ARG is non-nil, unused imports will be commented, otherwise
-they will be removed completely."
-  (interactive "P")
-  (save-excursion
-    (let ((cur-buffer (current-buffer)) flymake-state lines)
-      (when (boundp 'flymake-mode)
-        (setq flymake-state flymake-mode)
-        (flymake-mode -1))
-      (save-some-buffers nil (lambda () (equal cur-buffer (current-buffer))))
-      (if (buffer-modified-p)
-          (message "Cannot operate on unsaved buffer")
-        (setq lines (go-unused-imports-lines))
-        (dolist (import lines)
-          (go--goto-line import)
-          (beginning-of-line)
-          (if arg
-              (comment-region (line-beginning-position) (line-end-position))
-            (go--delete-whole-line)))
-        (message "Removed %d imports" (length lines)))
-      (if flymake-state (flymake-mode 1)))))
-
-(defun godef--find-file-line-column (specifier other-window)
-  "Given a file name in the format of `filename:line:column',
-visit FILENAME and go to line LINE and column COLUMN."
-  (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))
-      ;; We've only been given a directory name
-      (funcall (if other-window #'find-file-other-window #'find-file) specifier)
-    (let ((filename (match-string 1 specifier))
-          (line (string-to-number (match-string 2 specifier)))
-          (column (string-to-number (match-string 3 specifier))))
-      (funcall (if other-window #'find-file-other-window #'find-file) filename)
-      (go--goto-line line)
-      (beginning-of-line)
-      (forward-char (1- column))
-      (if (buffer-modified-p)
-          (message "Buffer is modified, file position might not have been correct")))))
-
-(defun godef--call (point)
-  "Call godef, acquiring definition position and expression
-description at POINT."
-  (if (not (buffer-file-name (go--coverage-origin-buffer)))
-      (error "Cannot use godef on a buffer without a file name")
-    (let ((outbuf (generate-new-buffer "*godef*"))
-          (coding-system-for-read 'utf-8)
-          (coding-system-for-write 'utf-8))
-      (prog2
-          (call-process-region (point-min)
-                               (point-max)
-                               godef-command
-                               nil
-                               outbuf
-                               nil
-                               "-i"
-                               "-t"
-                               "-f"
-                               (file-truename (buffer-file-name (go--coverage-origin-buffer)))
-                               "-o"
-                               ;; Emacs point and byte positions are 1-indexed.
-                               (number-to-string (1- (position-bytes point))))
-          (with-current-buffer outbuf
-            (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n"))
-        (kill-buffer outbuf)))))
-
-(defun godef--successful-p (output)
-  (not (or (string= "-" output)
-           (string= "godef: no identifier found" output)
-           (go--string-prefix-p "godef: no declaration found for " output)
-           (go--string-prefix-p "error finding import path for " output))))
-
-(defun godef--error (output)
-  (cond
-   ((godef--successful-p output)
-    nil)
-   ((string= "-" output)
-    "godef: expression is not defined anywhere")
-   (t
-    output)))
-
-(defun godef-describe (point)
-  "Describe the expression at POINT."
-  (interactive "d")
-  (condition-case nil
-      (let ((description (cdr (butlast (godef--call point) 1))))
-        (if (not description)
-            (message "No description found for expression at point")
-          (message "%s" (mapconcat #'identity description "\n"))))
-    (file-error (message "Could not run godef binary"))))
-
-(defun godef-jump (point &optional other-window)
-  "Jump to the definition of the expression at POINT."
-  (interactive "d")
-  (condition-case nil
-      (let ((file (car (godef--call point))))
-        (if (not (godef--successful-p file))
-            (message "%s" (godef--error file))
-          (push-mark)
-          (if (eval-when-compile (fboundp 'xref-push-marker-stack))
-              ;; TODO: Integrate this facility with XRef.
-              (xref-push-marker-stack)
-            (ring-insert find-tag-marker-ring (point-marker)))
-          (godef--find-file-line-column file other-window)))
-    (file-error (message "Could not run godef binary"))))
-
-(defun godef-jump-other-window (point)
-  (interactive "d")
-  (godef-jump point t))
-
-(defun go--goto-line (line)
-  (goto-char (point-min))
-  (forward-line (1- line)))
-
-(defun go--line-column-to-point (line column)
-  (save-excursion
-    (go--goto-line line)
-    (forward-char (1- column))
-    (point)))
-
-(cl-defstruct go--covered
-  start-line start-column end-line end-column covered count)
-
-(defun go--coverage-file ()
-  "Return the coverage file to use, either by reading it from the
-current coverage buffer or by prompting for it."
-  (if (boundp 'go--coverage-current-file-name)
-      go--coverage-current-file-name
-    (read-file-name "Coverage file: " nil nil t)))
-
-(defun go--coverage-origin-buffer ()
-  "Return the buffer to base the coverage on."
-  (or (buffer-base-buffer) (current-buffer)))
-
-(defun go--coverage-face (count divisor)
-  "Return the intensity face for COUNT when using DIVISOR
-to scale it to a range [0,10].
-
-DIVISOR scales the absolute cover count to values from 0 to 10.
-For DIVISOR = 0 the count will always translate to 8."
-  (let* ((norm (cond
-                ((= count 0)
-                 -0.1) ;; Uncovered code, set to -0.1 so n becomes 0.
-                ((= divisor 0)
-                 0.8) ;; covermode=set, set to 0.8 so n becomes 8.
-                (t
-                 (/ (log count) divisor))))
-         (n (1+ (floor (* norm 9))))) ;; Convert normalized count [0,1] to intensity [0,10]
-    (concat "go-coverage-" (number-to-string n))))
-
-(defun go--coverage-make-overlay (range divisor)
-  "Create a coverage overlay for a RANGE of covered/uncovered code.
-Use DIVISOR to scale absolute counts to a [0,10] scale."
-  (let* ((count (go--covered-count range))
-         (face (go--coverage-face count divisor))
-         (ov (make-overlay (go--line-column-to-point (go--covered-start-line range)
-                                                     (go--covered-start-column range))
-                           (go--line-column-to-point (go--covered-end-line range)
-                                                     (go--covered-end-column range)))))
-
-    (overlay-put ov 'face face)
-    (overlay-put ov 'help-echo (format "Count: %d" count))))
-
-(defun go--coverage-clear-overlays ()
-  "Remove existing overlays and put a single untracked overlay
-over the entire buffer."
-  (remove-overlays)
-  (overlay-put (make-overlay (point-min) (point-max))
-               'face
-               'go-coverage-untracked))
-
-(defun go--coverage-parse-file (coverage-file file-name)
-  "Parse COVERAGE-FILE and extract coverage information and
-divisor for FILE-NAME."
-  (let (ranges
-        (max-count 0))
-    (with-temp-buffer
-      (insert-file-contents coverage-file)
-      (go--goto-line 2) ;; Skip over mode
-      (while (not (eobp))
-        (let* ((parts (split-string (buffer-substring (point-at-bol) (point-at-eol)) ":"))
-               (file (car parts))
-               (rest (split-string (nth 1 parts) "[., ]")))
-
-          (cl-destructuring-bind
-              (start-line start-column end-line end-column num count)
-              (mapcar #'string-to-number rest)
-
-            (when (string= (file-name-nondirectory file) file-name)
-              (if (> count max-count)
-                  (setq max-count count))
-              (push (make-go--covered :start-line start-line
-                                      :start-column start-column
-                                      :end-line end-line
-                                      :end-column end-column
-                                      :covered (/= count 0)
-                                      :count count)
-                    ranges)))
-
-          (forward-line)))
-
-      (list ranges (if (> max-count 0) (log max-count) 0)))))
-
-(defun go-coverage (&optional coverage-file)
-  "Open a clone of the current buffer and overlay it with
-coverage information gathered via go test -coverprofile=COVERAGE-FILE.
-
-If COVERAGE-FILE is nil, it will either be inferred from the
-current buffer if it's already a coverage buffer, or be prompted
-for."
-  (interactive)
-  (let* ((cur-buffer (current-buffer))
-         (origin-buffer (go--coverage-origin-buffer))
-         (gocov-buffer-name (concat (buffer-name origin-buffer) "<gocov>"))
-         (coverage-file (or coverage-file (go--coverage-file)))
-         (ranges-and-divisor (go--coverage-parse-file
-                              coverage-file
-                              (file-name-nondirectory (buffer-file-name origin-buffer))))
-         (cov-mtime (nth 5 (file-attributes coverage-file)))
-         (cur-mtime (nth 5 (file-attributes (buffer-file-name origin-buffer)))))
-
-    (if (< (float-time cov-mtime) (float-time cur-mtime))
-        (message "Coverage file is older than the source file."))
-
-    (with-current-buffer (or (get-buffer gocov-buffer-name)
-                             (make-indirect-buffer origin-buffer gocov-buffer-name t))
-      (set (make-local-variable 'go--coverage-current-file-name) coverage-file)
-
-      (save-excursion
-        (go--coverage-clear-overlays)
-        (dolist (range (car ranges-and-divisor))
-          (go--coverage-make-overlay range (cadr ranges-and-divisor))))
-
-      (if (not (eq cur-buffer (current-buffer)))
-          (display-buffer (current-buffer) `(,go-coverage-display-buffer-func))))))
-
-(defun go-goto-function (&optional arg)
-  "Go to the function defintion (named or anonymous) surrounding point.
-
-If we are on a docstring, follow the docstring down.
-If no function is found, assume that we are at the top of a file
-and search forward instead.
-
-If point is looking at the func keyword of an anonymous function,
-go to the surrounding function.
-
-If ARG is non-nil, anonymous functions are ignored."
-  (interactive "P")
-  (let ((p (point)))
-    (cond
-     ((save-excursion
-        (beginning-of-line)
-        (looking-at "^//"))
-      ;; In case we are looking at the docstring, move on forward until we are
-      ;; not anymore
-      (beginning-of-line)
-      (while (looking-at "^//")
-        (forward-line 1))
-      ;; If we are still not looking at a function, retry by calling self again.
-      (when (not (looking-at "\\<func\\>"))
-        (go-goto-function arg)))
-
-     ;; If we're already looking at an anonymous func, look for the
-     ;; surrounding function.
-     ((and (looking-at "\\<func\\>")
-           (not (looking-at "^func\\>")))
-      (re-search-backward "\\<func\\>" nil t))
-
-     ((not (looking-at "\\<func\\>"))
-      ;; If point is on the "func" keyword, step back a word and retry
-      (if (string= (symbol-name (symbol-at-point)) "func")
-          (backward-word)
-        ;; If we are not looking at the beginning of a function line, do a regexp
-        ;; search backwards
-        (re-search-backward "\\<func\\>" nil t))
-
-      ;; If nothing is found, assume that we are at the top of the file and
-      ;; should search forward instead.
-      (when (not (looking-at "\\<func\\>"))
-        (re-search-forward "\\<func\\>" nil t)
-        (go--forward-word -1))
-
-      ;; If we have landed at an anonymous function, it is possible that we
-      ;; were not inside it but below it. If we were not inside it, we should
-      ;; go to the containing function.
-      (while (and (not (go--in-function-p p))
-                  (not (looking-at "^func\\>")))
-        (go-goto-function arg)))))
-
-  (cond
-   ((go-in-comment-p)
-    ;; If we are still in a comment, redo the call so that we get out of it.
-    (go-goto-function arg))
-
-   ((and (looking-at "\\<func(") arg)
-    ;; If we are looking at an anonymous function and a prefix argument has
-    ;; been supplied, redo the call so that we skip the anonymous function.
-    (go-goto-function arg))))
-
-(defun go--goto-opening-curly-brace ()
-  ;; Find the { that starts the function, i.e., the next { that isn't
-  ;; preceded by struct or interface, or a comment or struct tag.  BUG:
-  ;; breaks if there's a comment between the struct/interface keyword and
-  ;; bracket, like this:
-  ;;
-  ;;     struct /* why? */ {
-  (go--goto-return-values)
-  (while (progn
-           (skip-chars-forward "^{")
-           (forward-char)
-           (or (go-in-string-or-comment-p)
-               (looking-back "\\(struct\\|interface\\)\\s-*{"
-                             (line-beginning-position)))))
-  (backward-char))
-
-(defun go--in-function-p (compare-point)
-  "Return t if COMPARE-POINT lies inside the function immediately surrounding point."
-  (save-excursion
-    (when (not (looking-at "\\<func\\>"))
-      (go-goto-function))
-    (let ((start (point)))
-      (go--goto-opening-curly-brace)
-
-      (unless (looking-at "{")
-        (error "Expected to be looking at opening curly brace"))
-      (forward-list 1)
-      (and (>= compare-point start)
-           (<= compare-point (point))))))
-
-(defun go-goto-function-name (&optional arg)
-  "Go to the name of the current function.
-
-If the function is a test, place point after 'Test'.
-If the function is anonymous, place point on the 'func' keyword.
-
-If ARG is non-nil, anonymous functions are skipped."
-  (interactive "P")
-  (when (not (looking-at "\\<func\\>"))
-    (go-goto-function arg))
-  ;; If we are looking at func( we are on an anonymous function and
-  ;; nothing else should be done.
-  (when (not (looking-at "\\<func("))
-    (let ((words 1)
-          (chars 1))
-      (when (looking-at "\\<func (")
-        (setq words 3
-              chars 2))
-      (go--forward-word words)
-      (forward-char chars)
-      (when (looking-at "Test")
-        (forward-char 4)))))
-
-(defun go-goto-arguments (&optional arg)
-  "Go to the arguments of the current function.
-
-If ARG is non-nil, anonymous functions are skipped."
-  (interactive "P")
-  (go-goto-function-name arg)
-  (go--forward-word 1)
-  (forward-char 1))
-
-(defun go--goto-return-values (&optional arg)
-  "Go to the declaration of return values for the current function."
-  (go-goto-arguments arg)
-  (backward-char)
-  (forward-list)
-  (forward-char))
-
-(defun go-goto-return-values (&optional arg)
-  "Go to the return value declaration of the current function.
-
-If there are multiple ones contained in a parenthesis, enter the parenthesis.
-If there is none, make space for one to be added.
-
-If ARG is non-nil, anonymous functions are skipped."
-  (interactive "P")
-  (go--goto-return-values arg)
-
-  ;; Opening parenthesis, enter it
-  (when (looking-at "(")
-    (forward-char 1))
-
-  ;; No return arguments, add space for adding
-  (when (looking-at "{")
-    (insert " ")
-    (backward-char 1)))
-
-(defun go-goto-method-receiver (&optional arg)
-  "Go to the receiver of the current method.
-
-If there is none, add parenthesis to add one.
-
-Anonymous functions cannot have method receivers, so when this is called
-interactively anonymous functions will be skipped.  If called programmatically,
-an error is raised unless ARG is non-nil."
-  (interactive "P")
-
-  (when (and (not (called-interactively-p 'interactive))
-             (not arg)
-             (go--in-anonymous-funcion-p))
-    (error "Anonymous functions cannot have method receivers"))
-
-  (go-goto-function t)  ; Always skip anonymous functions
-  (forward-char 5)
-  (when (not (looking-at "("))
-    (save-excursion
-      (insert "() ")))
-  (forward-char 1))
-
-(defun go-goto-docstring (&optional arg)
-  "Go to the top of the docstring of the current function.
-
-If there is none, add one beginning with the name of the current function.
-
-Anonymous functions do not have docstrings, so when this is called
-interactively anonymous functions will be skipped.  If called programmatically,
-an error is raised unless ARG is non-nil."
-  (interactive "P")
-
-  (when (and (not (called-interactively-p 'interactive))
-             (not arg)
-             (go--in-anonymous-funcion-p))
-    (error "Anonymous functions do not have docstrings"))
-
-  (go-goto-function t)
-  (forward-line -1)
-  (beginning-of-line)
-
-  (while (looking-at "^//")
-    (forward-line -1))
-  (forward-line 1)
-  (beginning-of-line)
-
-  (cond
-   ;; If we are looking at an empty comment, add a single space in front of it.
-   ((looking-at "^//$")
-    (forward-char 2)
-    (insert (format " %s " (go--function-name t))))
-   ;; If we are not looking at the function signature, we are looking at a docstring.
-   ;; Move to the beginning of the first word of it.
-   ((not (looking-at "^func"))
-    (forward-char 3))
-   ;; If we are still at the function signature, we should add a new docstring.
-   (t
-    (forward-line -1)
-    (newline)
-    (insert "// ")
-    (insert (go--function-name t)))))
-
-(defun go--function-name (&optional arg)
-  "Return the name of the surrounding function.
-
-If ARG is non-nil, anonymous functions will be ignored and the
-name returned will be that of the top-level function.  If ARG is
-nil and the surrounding function is anonymous, nil will be
-returned."
-  (when (or (not (go--in-anonymous-funcion-p))
-            arg)
-    (save-excursion
-      (go-goto-function-name t)
-      (symbol-name (symbol-at-point)))))
-
-(defun go--in-anonymous-funcion-p ()
-  "Return t if point is inside an anonymous function, nil otherwise."
-  (save-excursion
-    (go-goto-function)
-    (looking-at "\\<func(")))
-
-(defun go-guess-gopath (&optional buffer)
-  "Determine a suitable GOPATH for BUFFER, or the current buffer if BUFFER is nil.
-
-This function supports gb-based projects as well as Godep, in
-addition to ordinary uses of GOPATH."
-  (with-current-buffer (or buffer (current-buffer))
-    (let ((gopath (cl-some (lambda (el) (funcall el))
-                           go-guess-gopath-functions)))
-      (if gopath
-          (mapconcat
-           (lambda (el) (file-truename el))
-           gopath
-           path-separator)))))
-
-(defun go-plain-gopath ()
-  "Detect a normal GOPATH, by looking for the first `src'
-directory up the directory tree."
-  (let ((d (locate-dominating-file buffer-file-name "src")))
-    (if d
-        (list d))))
-
-(defun go-godep-gopath ()
-  "Detect a Godeps workspace by looking for Godeps/_workspace up
-the directory tree. The result is combined with that of
-`go-plain-gopath'."
-  (let* ((d (locate-dominating-file buffer-file-name "Godeps"))
-         (workspace (concat d
-                            (file-name-as-directory "Godeps")
-                            (file-name-as-directory "_workspace"))))
-    (if (and d
-             (file-exists-p workspace))
-        (list workspace
-              (locate-dominating-file buffer-file-name "src")))))
-
-(defun go-gb-gopath ()
-  "Detect a gb project."
-  (or (go--gb-vendor-gopath)
-      (go--gb-vendor-gopath-reverse)))
-
-(defun go--gb-vendor-gopath ()
-  (let* ((d (locate-dominating-file buffer-file-name "src"))
-         (vendor (concat d (file-name-as-directory "vendor"))))
-    (if (and d
-             (file-exists-p vendor))
-        (list d vendor))))
-
-(defun go--gb-vendor-gopath-reverse ()
-  (let* ((d (locate-dominating-file buffer-file-name "vendor"))
-         (src (concat d (file-name-as-directory "src"))))
-    (if (and d
-             (file-exists-p src))
-        (list d (concat d
-                        (file-name-as-directory "vendor"))))))
-
-(defun go-wgo-gopath ()
-  "Detect a wgo project."
-  (or (go--wgo-gocfg "src")
-      (go--wgo-gocfg "vendor")))
-
-(defun go--wgo-gocfg (needle)
-  (let* ((d (locate-dominating-file buffer-file-name needle))
-         (gocfg (concat d (file-name-as-directory ".gocfg"))))
-    (if (and d
-             (file-exists-p gocfg))
-        (with-temp-buffer
-          (insert-file-contents (concat gocfg "gopaths"))
-          (append
-           (mapcar (lambda (el) (concat d (file-name-as-directory el))) (split-string (buffer-string) "\n" t))
-           (list (go-original-gopath)))))))
-
-(defun go-set-project (&optional buffer)
-  "Set GOPATH based on `go-guess-gopath' for BUFFER, or the current buffer if BUFFER is nil.
-
-If go-guess-gopath returns nil, that is if it couldn't determine
-a valid value for GOPATH, GOPATH will be set to the initial value
-of when Emacs was started.
-
-This function can for example be used as a
-projectile-switch-project-hook, or simply be called manually when
-switching projects."
-  (interactive)
-  (let ((gopath (or (go-guess-gopath buffer)
-                    (go-original-gopath))))
-    (setenv "GOPATH" gopath)
-    (message "Set GOPATH to %s" gopath)))
-
-(defun go-reset-gopath ()
-  "Reset GOPATH to the value it had when Emacs started."
-  (interactive)
-  (let ((gopath (go-original-gopath)))
-    (setenv "GOPATH" gopath)
-    (message "Set GOPATH to %s" gopath)))
-
-(defun go-original-gopath ()
-  "Return the original value of GOPATH from when Emacs was started."
-  (let ((process-environment initial-environment)) (getenv "GOPATH")))
-
-(defun go--insert-modified-files ()
-  "Insert the contents of each modified Go buffer into the
-current buffer in the format specified by guru's -modified flag."
-  (mapc #'(lambda (b)
-            (and (buffer-modified-p b)
-                 (buffer-file-name b)
-                 (string= (file-name-extension (buffer-file-name b)) "go")
-                 (go--insert-modified-file (buffer-file-name b) b)))
-        (buffer-list)))
-
-(defun go--insert-modified-file (name buffer)
-  (insert (format "%s\n%d\n" name (go--buffer-size-bytes buffer)))
-  (insert-buffer-substring buffer))
-
-(defun go--buffer-size-bytes (&optional buffer)
-  (message "buffer; %s" buffer)
-  "Return the number of bytes in the current buffer.
-If BUFFER, return the number of characters in that buffer instead."
-  (with-current-buffer (or buffer (current-buffer))
-    (1- (position-bytes (point-max)))))
-
-(defvar go-dot-mod-mode-map
-  (let ((map (make-sparse-keymap)))
-    map)
-  "Keymap for `go-dot-mod-mode'.")
-
-(defvar go-dot-mod-mode-syntax-table
-  (let ((st (make-syntax-table)))
-    ;; handle '//' comment syntax
-    (modify-syntax-entry ?/ ". 124b" st)
-    (modify-syntax-entry ?\n "> b" st)
-    st)
-  "Syntax table for `go-dot-mod-mode'.")
-
-(defconst go-dot-mod-mode-keywords
-  '("module" "go" "require" "replace" "exclude")
-  "All keywords for go.mod files.  Used for font locking.")
-
-(defvar go-dot-mod-font-lock-keywords
-  `(
-    (,(concat "^\\s-*" (regexp-opt go-dot-mod-mode-keywords t) "\\s-") . font-lock-keyword-face))
-  "Keyword highlighting specification for `go-dot-mod-mode'.")
-
-;;;###autoload
-(define-derived-mode go-dot-mod-mode fundamental-mode "Go Mod"
-  "A major mode for editing go.mod files."
-  :syntax-table go-dot-mod-mode-syntax-table
-  (set (make-local-variable 'comment-start) "// ")
-  (set (make-local-variable 'comment-end)   "")
-  (set (make-local-variable 'comment-use-syntax) t)
-  (set (make-local-variable 'comment-start-skip) "\\(//+\\)\\s *")
-
-  (set (make-local-variable 'font-lock-defaults)
-       '(go-dot-mod-font-lock-keywords))
-  (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
-
-  ;; Go style
-  (setq indent-tabs-mode t)
-
-  ;; we borrow the go-mode-indent function so we need this buffer cache
-  (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
-  (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t))
-
-;;;###autoload
-(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-dot-mod-mode))
-
-;; Polyfills for functions added in Emacs 26.  Remove these once we don’t
-;; support Emacs 25 any more.
-(defalias 'go--file-local-name
-  (if (fboundp 'file-local-name) #'file-local-name
-    (lambda (file) (or (file-remote-p file 'localname) file))))
-
-(defalias 'go--make-nearby-temp-file
-  (if (fboundp 'make-nearby-temp-file) #'make-nearby-temp-file
-    (lambda (prefix &optional dir-flag suffix)
-      (let ((temporary-file-directory (go--temporary-file-directory)))
-        (make-temp-file prefix dir-flag suffix)))))
-
-(defalias 'go--temporary-file-directory
-  (if (fboundp 'temporary-file-directory) #'temporary-file-directory
-    (lambda ()
-      (let ((remote (file-remote-p default-directory)))
-        (if remote
-            ;; Assume that /tmp is a temporary directory on the remote host.
-            ;; This won’t work on Windows.
-            (concat remote "/tmp")
-          temporary-file-directory)))))
-
-(provide 'go-mode)
-
-;;; go-mode.el ends here
diff --git a/elpa/go-mode-20230624.2315/go-mode-autoloads.el b/elpa/go-mode-20230624.2315/go-mode-autoloads.el
new file mode 100644 (file)
index 0000000..3c049d9
--- /dev/null
@@ -0,0 +1,122 @@
+;;; go-mode-autoloads.el --- automatically extracted autoloads  -*- lexical-binding: t -*-
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+                         (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "go-mode" "go-mode.el" (0 0 0 0))
+;;; Generated autoloads from go-mode.el
+
+(autoload 'go-mode "go-mode" "\
+Major mode for editing Go source text.
+
+This mode provides (not just) basic editing capabilities for
+working with Go code. It offers almost complete syntax
+highlighting, indentation that is almost identical to gofmt and
+proper parsing of the buffer content to allow features such as
+navigation by function, manipulation of comments or detection of
+strings.
+
+In addition to these core features, it offers various features to
+help with writing Go code. You can directly run buffer content
+through gofmt, read godoc documentation from within Emacs, modify
+and clean up the list of package imports or interact with the
+Playground (uploading and downloading pastes).
+
+The following extra functions are defined:
+
+- `gofmt'
+- `godoc' and `godoc-at-point'
+- `go-import-add'
+- `go-goto-arguments'
+- `go-goto-docstring'
+- `go-goto-function'
+- `go-goto-function-name'
+- `go-goto-imports'
+- `go-goto-return-values'
+- `go-goto-method-receiver'
+- `go-play-buffer' and `go-play-region'
+- `go-download-play'
+- `godef-describe' and `godef-jump'
+- `go-coverage'
+
+If you want to automatically run `gofmt' before saving a file,
+add the following hook to your Emacs configuration:
+
+\(add-hook 'before-save-hook #'gofmt-before-save)
+
+If you want to use `godef-jump' instead of etags (or similar),
+consider binding godef-jump to `M-.', which is the default key
+for `find-tag':
+
+\(add-hook 'go-mode-hook (lambda ()
+                          (local-set-key (kbd \"M-.\") #'godef-jump)))
+
+Please note that godef is an external dependency. You can install
+it with
+
+go get github.com/rogpeppe/godef
+
+
+If you're looking for even more integration with Go, namely
+on-the-fly syntax checking, auto-completion and snippets, it is
+recommended that you look at flycheck
+\(see URL `https://github.com/flycheck/flycheck') or flymake in combination
+with goflymake (see URL `https://github.com/dougm/goflymake'), gocode
+\(see URL `https://github.com/nsf/gocode'), go-eldoc
+\(see URL `github.com/syohex/emacs-go-eldoc') and yasnippet-go
+\(see URL `https://github.com/dominikh/yasnippet-go')
+
+\(fn)" t nil)
+
+(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
+
+(autoload 'gofmt-before-save "go-mode" "\
+Add this to .emacs to run gofmt on the current buffer when saving:
+\(add-hook 'before-save-hook 'gofmt-before-save).
+
+Note that this will cause ‘go-mode’ to get loaded the first time
+you save any file, kind of defeating the point of autoloading." t nil)
+
+(autoload 'godoc "go-mode" "\
+Show Go documentation for QUERY, much like \\<go-mode-map>\\[man].
+
+\(fn QUERY)" t nil)
+
+(autoload 'go-download-play "go-mode" "\
+Download a paste from the playground and insert it in a Go buffer.
+Tries to look for a URL at point.
+
+\(fn URL)" t nil)
+
+(autoload 'go-dot-mod-mode "go-mode" "\
+A major mode for editing go.mod files.
+
+\(fn)" t nil)
+
+(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-dot-mod-mode))
+
+(autoload 'go-dot-work-mode "go-mode" "\
+A major mode for editor go.work files.
+
+\(fn)" t nil)
+
+(add-to-list 'auto-mode-alist '("go\\.work\\'" . go-dot-work-mode))
+
+(register-definition-prefixes "go-mode" '("go-" "god" "gofmt"))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("go-mode-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; go-mode-autoloads.el ends here
diff --git a/elpa/go-mode-20230624.2315/go-mode-pkg.el b/elpa/go-mode-20230624.2315/go-mode-pkg.el
new file mode 100644 (file)
index 0000000..73f169e
--- /dev/null
@@ -0,0 +1,14 @@
+(define-package "go-mode" "20230624.2315" "Major mode for the Go programming language"
+  '((emacs "26.1"))
+  :commit "f21347ae9cf68ea33031bf5125896e8f16c8183c" :authors
+  '(("The go-mode Authors"))
+  :maintainers
+  '(("The go-mode Authors"))
+  :maintainer
+  '("The go-mode Authors")
+  :keywords
+  '("languages" "go")
+  :url "https://github.com/dominikh/go-mode.el")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
diff --git a/elpa/go-mode-20230624.2315/go-mode.el b/elpa/go-mode-20230624.2315/go-mode.el
new file mode 100644 (file)
index 0000000..d1152eb
--- /dev/null
@@ -0,0 +1,3056 @@
+;;; go-mode.el --- Major mode for the Go programming language
+
+;;; Commentary:
+
+;; Copyright 2013 The go-mode Authors.  All rights reserved.
+;; Use of this source code is governed by a BSD-style
+;; license that can be found in the LICENSE file.
+
+;; Author: The go-mode Authors
+;; Version: 1.6.0
+;; Keywords: languages go
+;; Package-Requires: ((emacs "26.1"))
+;; URL: https://github.com/dominikh/go-mode.el
+;;
+;; This file is not part of GNU Emacs.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'compile)
+(require 'etags)
+(require 'ffap)
+(require 'find-file)
+(require 'ring)
+(require 'url)
+(require 'xref)
+
+
+(eval-when-compile
+  (defmacro go--forward-word (&optional arg)
+   (if (fboundp 'forward-word-strictly)
+       `(forward-word-strictly ,arg)
+     `(forward-word ,arg))))
+
+(defun go--delete-whole-line (&optional arg)
+  "Delete the current line without putting it in the `kill-ring'.
+Derived from function `kill-whole-line'.  ARG is defined as for that
+function."
+  (setq arg (or arg 1))
+  (if (and (> arg 0)
+           (eobp)
+           (save-excursion (forward-visible-line 0) (eobp)))
+      (signal 'end-of-buffer nil))
+  (if (and (< arg 0)
+           (bobp)
+           (save-excursion (end-of-visible-line) (bobp)))
+      (signal 'beginning-of-buffer nil))
+  (cond ((zerop arg)
+         (delete-region (progn (forward-visible-line 0) (point))
+                        (progn (end-of-visible-line) (point))))
+        ((< arg 0)
+         (delete-region (progn (end-of-visible-line) (point))
+                        (progn (forward-visible-line (1+ arg))
+                               (unless (bobp)
+                                 (backward-char))
+                               (point))))
+        (t
+         (delete-region (progn (forward-visible-line 0) (point))
+                        (progn (forward-visible-line arg) (point))))))
+
+(defun go-goto-opening-parenthesis (&optional _legacy-unused)
+  "Move up one level of parentheses.
+
+Return non-nil if there was a paren to move up to."
+  ;; The old implementation of go-goto-opening-parenthesis had an
+  ;; optional argument to speed up the function.  It didn't change the
+  ;; function's outcome.
+
+  ;; Silently fail if there's no matching opening parenthesis.
+  (let ((open-char (nth 1 (syntax-ppss))))
+    (when open-char
+      (goto-char open-char))))
+
+
+(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
+(defconst go--max-dangling-operator-length 2
+  "The maximum length of dangling operators.
+This must be at least the length of the longest string matched by
+‘go-dangling-operators-regexp’ and must be updated whenever that
+constant is changed.")
+
+(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 "\\_<func\\_>\\s *\\(" go-identifier-regexp "\\)"))
+(defconst go-func-meth-regexp (concat
+                               "\\_<func\\_>\\s *\\(?:(\\s *"
+                               "\\(" go-identifier-regexp "\\s +\\)?" go-type-regexp
+                               "\\s *)\\s *\\)?\\("
+                               go-identifier-regexp
+                               "\\)("))
+
+(defconst go--comment-start-regexp "[[:space:]]*\\(?:/[/*]\\)")
+(defconst go--case-regexp "\\([[:space:]]*case\\([[:space:]]\\|$\\)\\)")
+(defconst go--case-or-default-regexp (concat "\\(" go--case-regexp "\\|"  "[[:space:]]*default:\\)"))
+
+(defconst go-builtins
+  '("append"  "cap"    "clear"   "close" "complex"
+    "copy"    "delete" "imag"    "len"   "make"
+    "max"     "min"    "new"     "panic" "print"
+    "println" "real"   "recover")
+  "All built-in functions in the Go language.  Used for font locking.")
+
+(defconst go-mode-keywords
+  '("break"    "default"     "func"   "interface" "select"
+    "case"     "defer"       "go"     "map"       "struct"
+    "chan"     "else"        "goto"   "package"   "switch"
+    "const"    "fallthrough" "if"     "range"     "type"
+    "continue" "for"         "import" "return"    "var")
+  "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 "\\)"))
+
+(defvar go-dangling-cache)
+(defvar go-godoc-history nil)
+(defvar go--coverage-current-file-name)
+
+(defgroup go nil
+  "Major mode for editing Go code."
+  :link '(url-link "https://github.com/dominikh/go-mode.el")
+  :group 'languages)
+
+(defgroup go-cover nil
+  "Options specific to `cover`."
+  :group 'go)
+
+(defgroup godoc nil
+  "Options specific to `godoc'."
+  :group 'go)
+
+(defcustom go-fontify-function-calls t
+  "Fontify function and method calls if this is non-nil."
+  :type 'boolean
+  :group 'go)
+
+(defcustom go-fontify-variables t
+  "Fontify variable declarations if this is non-nil."
+  :type 'boolean
+  :group 'go)
+
+(defcustom go-mode-hook nil
+  "Hook called by `go-mode'."
+  :type 'hook
+  :group 'go)
+
+(defcustom go-command "go"
+  "The 'go' command.
+Some users have multiple Go development trees and invoke the 'go'
+tool via a wrapper that sets GOROOT and GOPATH based on the
+current directory.  Such users should customize this variable to
+point to the wrapper script."
+  :type 'string
+  :group 'go)
+
+(defcustom gofmt-command "gofmt"
+  "The 'gofmt' command.
+Some users may replace this with 'goimports'
+from https://golang.org/x/tools/cmd/goimports."
+  :type 'string
+  :group 'go)
+
+(defcustom gofmt-args nil
+  "Additional arguments to pass to gofmt."
+  :type '(repeat string)
+  :group 'go)
+
+(defcustom gofmt-show-errors 'buffer
+  "Where to display gofmt error output.
+It can either be displayed in its own buffer, in the echo area, or not at all.
+
+Please note that Emacs outputs to the echo area when writing
+files and will overwrite gofmt's echo output if used from inside
+a `before-save-hook'."
+  :type '(choice
+          (const :tag "Own buffer" buffer)
+          (const :tag "Echo area" echo)
+          (const :tag "None" nil))
+  :group 'go)
+
+(defcustom godef-command "godef"
+  "The 'godef' command."
+  :type 'string
+  :group 'go)
+
+(defcustom go-other-file-alist
+  '(("_test\\.go\\'" (".go"))
+    ("\\.go\\'" ("_test.go")))
+  "See the documentation of `ff-other-file-alist' for details."
+  :type '(repeat (list regexp (choice (repeat string) function)))
+  :group 'go)
+
+(defcustom go-packages-function 'go-packages-go-list
+  "Function called by `go-packages' to determine the list of available packages.
+This is used in e.g. tab completion in `go-import-add'.
+
+This package provides two functions: `go-packages-go-list' uses
+'go list all' to determine all Go packages. `go-packages-native' uses
+elisp to find all .a files in all /pkg/ directories.
+`go-packages-native' is obsolete as it doesn't behave correctly with
+the Go build cache or Go modules."
+  :type 'function
+  :package-version '(go-mode . 1.4.0)
+  :group 'go)
+
+(defcustom go-guess-gopath-functions (list #'go-plain-gopath)
+  "Functions to call in sequence to detect a project's GOPATH.
+
+The functions in this list will be called one after another,
+until a function returns non-nil.  The order of the functions in
+this list is important, as some project layouts may superficially
+look like others."
+  :type '(repeat function)
+  :group 'go)
+
+(make-obsolete-variable 'go-guess-gopath-functions "GOPATH has been deprecated in favour of Go modules." "1.7.0")
+
+(defcustom go-confirm-playground-uploads t
+  "Ask before uploading code to the public Go Playground.
+
+Set this to nil to upload without prompting."
+  :type 'boolean
+  :group 'go)
+
+(defcustom godoc-command "go doc"
+  "Which executable to use for `godoc'.
+This can be either an absolute path or an executable in PATH."
+  :type 'string
+  :group 'go)
+
+(defcustom godoc-and-godef-command "go doc"
+  "Which executable to use for `godoc-and-godef'.
+This can be either an absolute path or an executable in PATH."
+  :type 'string
+  :group 'go)
+
+(defcustom godoc-use-completing-read nil
+  "Provide auto-completion for godoc.
+Only really desirable when using `godoc' instead of `go doc'."
+  :type 'boolean
+  :group 'godoc)
+
+(defcustom godoc-reuse-buffer nil
+  "Reuse a single *godoc* buffer to display godoc-at-point calls.
+The default behavior is to open a separate buffer for each call."
+  :type 'boolean
+  :group 'godoc)
+
+(defcustom godoc-at-point-function #'godoc-and-godef
+  "Function to call to display the documentation for an
+identifier at a given position.
+
+This package provides two functions: `godoc-and-godef' uses a
+combination of godef and godoc to find the documentation.  This
+approach has several caveats.  See its documentation for more
+information.  The second function, `godoc-gogetdoc' uses an
+additional tool that correctly determines the documentation for
+any identifier.  It provides better results than
+`godoc-and-godef'."
+  :type 'function
+  :group 'godoc)
+
+(defun godoc-and-godef (point)
+  "Use a combination of godef and godoc to guess the documentation at POINT.
+
+Due to a limitation in godoc, it is not possible to differentiate
+between functions and methods, which may cause `godoc-at-point'
+to display more documentation than desired.  Furthermore, it
+doesn't work on package names or variables.
+
+Consider using ‘godoc-gogetdoc’ instead for more accurate results."
+  (condition-case nil
+      (let* ((output (godef--call point))
+             (file (car output))
+             (name-parts (split-string (cadr output) " "))
+             (first (car name-parts)))
+        (if (not (godef--successful-p file))
+            (message "%s" (godef--error file))
+          (go--godoc (format "%s %s"
+                         (file-name-directory file)
+                         (if (or (string= first "type") (string= first "const"))
+                             (cadr name-parts)
+                           (car name-parts)))
+                    godoc-and-godef-command)))
+    (file-error (message "Could not run godef binary"))))
+
+(defun godoc-gogetdoc (point)
+  "Use the gogetdoc tool to find the documentation for an identifier at POINT.
+
+You can install gogetdoc with 'go get -u github.com/zmb3/gogetdoc'."
+  (if (not (buffer-file-name (go--coverage-origin-buffer)))
+      ;; TODO: gogetdoc supports unsaved files, but not introducing
+      ;; new artificial files, so this limitation will stay for now.
+      (error "Cannot use gogetdoc on a buffer without a file name"))
+  (let ((posn (format "%s:#%d" (file-truename buffer-file-name) (1- (position-bytes point))))
+        (out (godoc--get-buffer "<at point>")))
+  (with-temp-buffer
+    (go--insert-modified-files)
+    (call-process-region (point-min) (point-max) "gogetdoc" nil out nil
+                         "-modified"
+                         (format "-pos=%s" posn)))
+  (with-current-buffer out
+    (goto-char (point-min))
+    (godoc-mode)
+    (display-buffer (current-buffer) t))))
+
+(defun go--kill-new-message (url)
+  "Make URL the latest kill and print a message."
+  (kill-new url)
+  (message "%s" url))
+
+(defcustom go-play-browse-function 'go--kill-new-message
+  "Function to call with the Playground URL.
+See `go-play-region' for more details."
+  :type '(choice
+          (const :tag "Nothing" nil)
+          (const :tag "Kill + Message" go--kill-new-message)
+          (const :tag "Browse URL" browse-url)
+          (function :tag "Call function"))
+  :group 'go)
+
+(defcustom go-coverage-display-buffer-func 'display-buffer-reuse-window
+  "How `go-coverage' should display the coverage buffer.
+See `display-buffer' for a list of possible functions."
+  :type 'function
+  :group 'go-cover)
+
+(defface go-coverage-untracked
+  '((t (:foreground "#505050")))
+  "Coverage color of untracked code."
+  :group 'go-cover)
+
+(defface go-coverage-0
+  '((t (:foreground "#c00000")))
+  "Coverage color for uncovered code."
+  :group 'go-cover)
+(defface go-coverage-1
+  '((t (:foreground "#808080")))
+  "Coverage color for covered code with weight 1."
+  :group 'go-cover)
+(defface go-coverage-2
+  '((t (:foreground "#748c83")))
+  "Coverage color for covered code with weight 2."
+  :group 'go-cover)
+(defface go-coverage-3
+  '((t (:foreground "#689886")))
+  "Coverage color for covered code with weight 3."
+  :group 'go-cover)
+(defface go-coverage-4
+  '((t (:foreground "#5ca489")))
+  "Coverage color for covered code with weight 4."
+  :group 'go-cover)
+(defface go-coverage-5
+  '((t (:foreground "#50b08c")))
+  "Coverage color for covered code with weight 5."
+  :group 'go-cover)
+(defface go-coverage-6
+  '((t (:foreground "#44bc8f")))
+  "Coverage color for covered code with weight 6."
+  :group 'go-cover)
+(defface go-coverage-7
+  '((t (:foreground "#38c892")))
+  "Coverage color for covered code with weight 7."
+  :group 'go-cover)
+(defface go-coverage-8
+  '((t (:foreground "#2cd495")))
+  "Coverage color for covered code with weight 8.
+For mode=set, all covered lines will have this weight."
+  :group 'go-cover)
+(defface go-coverage-9
+  '((t (:foreground "#20e098")))
+  "Coverage color for covered code with weight 9."
+  :group 'go-cover)
+(defface go-coverage-10
+  '((t (:foreground "#14ec9b")))
+  "Coverage color for covered code with weight 10."
+  :group 'go-cover)
+(defface go-coverage-covered
+  '((t (:foreground "#2cd495")))
+  "Coverage color of covered code."
+  :group 'go-cover)
+
+(defvar go-mode-syntax-table
+  (let ((st (make-syntax-table)))
+    (modify-syntax-entry ?+  "." st)
+    (modify-syntax-entry ?-  "." st)
+    (modify-syntax-entry ?%  "." st)
+    (modify-syntax-entry ?&  "." st)
+    (modify-syntax-entry ?|  "." st)
+    (modify-syntax-entry ?^  "." st)
+    (modify-syntax-entry ?!  "." st)
+    (modify-syntax-entry ?=  "." st)
+    (modify-syntax-entry ?<  "." st)
+    (modify-syntax-entry ?>  "." st)
+    (modify-syntax-entry ?/  ". 124b" st)
+    (modify-syntax-entry ?*  ". 23" st)
+    (modify-syntax-entry ?\n "> b" st)
+    (modify-syntax-entry ?\" "\"" st)
+    (modify-syntax-entry ?\' "\"" st)
+    (modify-syntax-entry ?`  "\"" st)
+    (modify-syntax-entry ?\\ "\\" st)
+    ;; TODO make _ a symbol constituent now that xemacs is gone
+    (modify-syntax-entry ?_  "w" st)
+
+    st)
+  "Syntax table for Go mode.")
+
+(defun go--fontify-type-switch-case-pre ()
+  "Move point to line following the end of case statement.
+
+This is used as an anchored font lock keyword PRE-MATCH-FORM. We
+expand the font lock region to include multiline type switch case
+statements."
+  (save-excursion
+    (beginning-of-line)
+    (while (or (looking-at "[[:space:]]*\\($\\|//\\)") (go--line-suffix-p ","))
+      (forward-line))
+    (when (go--line-suffix-p ":")
+      (forward-line))
+    (point)))
+
+(defun go--build-font-lock-keywords ()
+  ;; we cannot use 'symbols in regexp-opt because GNU Emacs <24
+  ;; doesn't understand that
+  (append
+   `(
+     ;; Match param lists in func signatures. This uses the
+     ;; MATCH-ANCHORED format (see `font-lock-keywords' docs).
+     ;;
+     ;; Parent/anchor match. It matches the param list opening "(".
+     (go--match-param-start
+      ;; Sub-matcher that matches individual params in the param list.
+      (go--fontify-param
+       ;; Pre-match form that runs before the first sub-match.
+       (go--fontify-param-pre)
+       ;; Post-match form that runs after last sub-match.
+       (go--fontify-param-post)
+       ;; Subexp 1 is the param variable name, if any.
+       (1 font-lock-variable-name-face nil t)
+       ;; Subexp 2 is the param type name, if any. We set the LAXMATCH
+       ;; flag to allow optional regex groups.
+       (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)
+
+     ;; Match name+type pairs, such as "foo bar" in "var foo bar".
+     (go--match-ident-type-pair 2 font-lock-type-face)
+
+     ;; An anchored matcher for type switch case clauses.
+     (go--match-type-switch-case
+      (go--fontify-type-switch-case
+       (go--fontify-type-switch-case-pre)
+       nil
+       (1 font-lock-type-face)))
+
+     ;; Match variable names in var decls, constant names in const
+     ;; decls, and type names in type decls.
+     (go--match-decl
+      (1 font-lock-variable-name-face nil t)
+      (2 font-lock-constant-face nil t)
+      (3 font-lock-type-face nil t))
+
+     (,(concat "\\_<" (regexp-opt go-mode-keywords t) "\\_>") . font-lock-keyword-face)
+     (,(concat "\\(\\_<" (regexp-opt go-builtins t) "\\_>\\)[[:space:]]*(") 1 font-lock-builtin-face)
+     (,(concat "\\_<" (regexp-opt go-constants t) "\\_>") . font-lock-constant-face)
+
+     ;; Function (not method) name
+     (,go-func-regexp 1 font-lock-function-name-face))
+
+   (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))
+     ;; Method name
+     `((,go-func-meth-regexp 2 font-lock-function-name-face)))
+
+   `(
+     ;; Raw string literal, needed for font-lock-syntactic-keywords
+     ("\\(`[^`]*`\\)" 1 font-lock-multiline)
+
+     ;; RHS of type alias.
+     (go--match-type-alias 2 font-lock-type-face)
+
+     ;; Arrays/slices: []<type> | [123]<type> | [some.Const]<type> | [someConst]<type> | [...]<type>
+     (,(concat "\\(?:^\\|[^[:word:][:multibyte:]]\\)\\[\\(?:[[:digit:]]+\\|" go-qualified-identifier-regexp "\\|" go-identifier-regexp "\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 1 font-lock-type-face)
+
+     ;; Unary "!"
+     ("\\(!\\)[^=]" 1 font-lock-negation-char-face)
+
+     ;; Composite literal type
+     (,(concat go-type-name-regexp "{") 1 font-lock-type-face)
+
+     ;; Map value type
+     (go--match-map-value 1 font-lock-type-face)
+
+     ;; Map key type
+     (,(concat "\\_<map\\_>\\[" go-type-name-regexp) 1 font-lock-type-face)
+
+     ;; Channel type
+     (,(concat "\\_<chan\\_>[[: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)
+
+     ;; Type assertion
+     (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face)
+
+     ;; Composite literal field names and label definitions.
+     (go--match-ident-colon 1 font-lock-constant-face)
+
+     ;; Labels in goto/break/continue
+     (,(concat "\\_<\\(?:goto\\|break\\|continue\\)\\_>[[:space:]]*\\(" go-label-regexp "\\)") 1 font-lock-constant-face))))
+
+(let ((m (define-prefix-command 'go-goto-map)))
+  (define-key m "a" #'go-goto-arguments)
+  (define-key m "d" #'go-goto-docstring)
+  (define-key m "f" #'go-goto-function)
+  (define-key m "i" #'go-goto-imports)
+  (define-key m "m" #'go-goto-method-receiver)
+  (define-key m "n" #'go-goto-function-name)
+  (define-key m "r" #'go-goto-return-values))
+
+(defvar go-mode-map
+  (let ((m (make-sparse-keymap)))
+    (unless (boundp 'electric-indent-chars)
+      (define-key m "}" #'go-mode-insert-and-indent)
+      (define-key m ")" #'go-mode-insert-and-indent))
+    (define-key m (kbd "C-c C-a") #'go-import-add)
+    (define-key m (kbd "C-c C-j") #'godef-jump)
+    (define-key m (kbd "C-x 4 C-c C-j") #'godef-jump-other-window)
+    (define-key m (kbd "C-c C-d") #'godef-describe)
+    (define-key m (kbd "C-c C-f") 'go-goto-map)
+    m)
+  "Keymap used by ‘go-mode’.")
+
+(easy-menu-define go-mode-menu go-mode-map
+  "Menu for Go mode."
+  '("Go"
+    ["Describe Expression"   godef-describe t]
+    ["Jump to Definition"    godef-jump t]
+    "---"
+    ["Add Import"            go-import-add t]
+    ["Go to Imports"         go-goto-imports t]
+    "---"
+    ("Playground"
+     ["Send Buffer"          go-play-buffer t]
+     ["Send Region"          go-play-region t]
+     ["Download"             go-download-play t])
+    "---"
+    ["Coverage"              go-coverage t]
+    ["Gofmt"                 gofmt t]
+    ["Godoc"                 godoc t]
+    "---"
+    ["Customize Mode"        (customize-group 'go) t]))
+
+(defun go-mode-insert-and-indent (key)
+  "Invoke the global binding of KEY, then reindent the line."
+
+  (interactive (list (this-command-keys)))
+  (call-interactively (lookup-key (current-global-map) key))
+  (indent-according-to-mode))
+
+(defmacro go-paren-level ()
+  `(car (syntax-ppss)))
+
+(defmacro go-in-string-or-comment-p ()
+  `(nth 8 (syntax-ppss)))
+
+(defmacro go-in-string-p ()
+  `(nth 3 (syntax-ppss)))
+
+(defmacro go-in-comment-p ()
+  `(nth 4 (syntax-ppss)))
+
+(defmacro go-goto-beginning-of-string-or-comment ()
+  `(goto-char (nth 8 (syntax-ppss))))
+
+(defun go--backward-irrelevant (&optional stop-at-string)
+  "Skip backwards over any characters that are irrelevant for
+indentation and related tasks.
+
+It skips over whitespace, comments, cases and labels and, if
+STOP-AT-STRING is not true, over strings."
+
+  (let (pos (start-pos (point)))
+    (skip-chars-backward "\n\s\t")
+    (if (and (save-excursion (beginning-of-line) (go-in-string-p))
+             (= (char-before) ?`)
+             (not stop-at-string))
+        (backward-char))
+    (if (and (go-in-string-p)
+             (not stop-at-string))
+        (go-goto-beginning-of-string-or-comment))
+    (if (looking-back "\\*/" (line-beginning-position))
+        (backward-char))
+    (if (go-in-comment-p)
+        (go-goto-beginning-of-string-or-comment))
+    (setq pos (point))
+    (beginning-of-line)
+    (if (or (looking-at (concat "^" go-label-regexp ":"))
+            (looking-at "^[[:space:]]*\\(case .+\\|default\\):"))
+        (end-of-line 0)
+      (goto-char pos))
+    (if (/= start-pos (point))
+        (go--backward-irrelevant stop-at-string))
+    (/= start-pos (point))))
+
+(defun go--buffer-narrowed-p ()
+  "Return non-nil if the current buffer is narrowed."
+  (/= (buffer-size)
+      (- (point-max)
+         (point-min))))
+
+(defun go-previous-line-has-dangling-op-p ()
+  "Return non-nil if the current line is a continuation line.
+The return value is cached based on the current `line-beginning-position'."
+  (let* ((line-begin (line-beginning-position))
+         (val (gethash line-begin go-dangling-cache 'nope)))
+    (when (or (go--buffer-narrowed-p) (equal val 'nope))
+      (save-excursion
+        (go--forward-line -1)
+        (if (go--current-line-has-dangling-op-p)
+            (setq val (line-end-position))
+          (setq val nil))
+
+        (if (not (go--buffer-narrowed-p))
+            (puthash line-begin val go-dangling-cache))))
+    val))
+
+(defun go--current-line-has-dangling-op-p ()
+  "Return non-nil if current line ends in a dangling operator.
+The return value is not cached."
+  (or
+   (and
+    (go--line-suffix-p go-dangling-operators-regexp)
+
+    ;; "=" does not behave like a dangling operator in decl statements.
+    (not (go--line-suffix-p "\\(?:var\\|type\\|const\\)[[:space:]].*="))
+
+    ;; Don't mistake "1234." for a dangling operator.
+    (not (go--line-suffix-p "[[:space:]]-?[[:digit:]][_0-9]*\\.")))
+
+   ;; treat comma as dangling operator in certain cases
+   (and
+    (go--line-suffix-p ",")
+    (save-excursion (end-of-line) (go--commas-indent-p)))))
+
+
+(defun go--commas-indent-p ()
+  "Return non-nil if in a context where dangling commas indent next line."
+  (not (or
+        (go--open-paren-position)
+        (go--in-composite-literal-p)
+        (go--in-case-clause-list-p)
+        (go--in-struct-definition-p))))
+
+(defun go--in-case-clause-list-p ()
+  "Return non-nil if inside a multi-line case cause list.
+
+This function is only concerned with list items on lines after the
+case keyword. It returns nil for the case line itself."
+  (save-excursion
+    (beginning-of-line)
+    (when (not (looking-at go--case-or-default-regexp))
+      (let (saw-colon)
+        ;; optionally skip line with the colon
+        (when (go--line-suffix-p ":")
+          (setq saw-colon t)
+          (forward-line -1))
+
+        ;; go backwards while at a comment or a line ending in comma
+        (while (and
+                (or
+                 (go--boring-line-p)
+                 (go--line-suffix-p ","))
+                (not (looking-at go--case-regexp))
+                (go--forward-line -1)))
+
+        (and
+         (looking-at-p go--case-regexp)
+         ;; we weren't in case list if first line ended in colon
+         ;; and the "case" line ended in colon
+         (not (and saw-colon (looking-at ".*:[[:space:]]*$"))))))))
+
+(defun go--in-composite-literal-p ()
+  "Return non-nil if point is in a composite literal."
+  (save-excursion
+    (save-match-data
+      (and
+       (go-goto-opening-parenthesis)
+
+       ;; Opening paren-like character is a curly.
+       (eq (char-after) ?{)
+
+       (or
+        ;; Curly is preceded by non space (e.g. "Foo{"), definitely
+        ;; composite literal.
+        (zerop (skip-syntax-backward " "))
+
+        ;; Curly preceded by comma or semicolon. This is a composite
+        ;; literal with implicit type name.
+        (looking-back "[,:]" (1- (point)))
+
+        ;; If we made it to the beginning of line we are either a naked
+        ;; block or a composite literal with implicit type name. If we
+        ;; are the latter, we must be contained in another composite
+        ;; literal.
+        (and (bolp) (go--in-composite-literal-p)))))))
+
+(defun go--in-paren-with-prefix-p (paren prefix)
+  (save-excursion
+    (and
+     (go-goto-opening-parenthesis)
+     (eq (char-after) paren)
+     (skip-syntax-backward " ")
+     (> (point) (length prefix))
+     (string= prefix (buffer-substring (- (point) (length prefix)) (point))))))
+
+(defun go--in-struct-definition-p ()
+  "Return non-nil if point is inside a struct definition."
+  (go--in-paren-with-prefix-p ?{ "struct"))
+
+(defun go--in-interface-p ()
+  "Return non-nil if point is inside an interface definition."
+  (go--in-paren-with-prefix-p ?{ "interface"))
+
+
+(defun go--in-type-switch-p ()
+  "Return non-nil if point is inside a type switch statement."
+  (go--in-paren-with-prefix-p ?{ ".(type)"))
+
+(defun go--open-paren-position ()
+  "Return non-nil if point is between '(' and ')'.
+
+The return value is the position of the opening paren."
+  (save-excursion
+    (let ((start-paren-level (go-paren-level)))
+      (and
+       (go-goto-opening-parenthesis)
+
+       ;; opening paren-like character is actually a paren
+       (eq (char-after) ?\()
+
+       ;; point is before the closing paren
+       (< (go-paren-level) start-paren-level)
+
+       (point)))))
+
+(defun go-indentation-at-point ()
+  "Return the appropriate indentation for the current line."
+  (save-excursion
+    (beginning-of-line)
+
+    (if (go-in-comment-p)
+        (go--multiline-comment-indent)
+      (go--indentation-at-point))))
+
+;; It's unfortunate that the user cannot reindent the current line to
+;; align with the previous line; however, if they could, then people
+;; who use reindent-then-newline-and-indent wouldn't be able to
+;; explicitly indent lines inside comments.
+(defun go--multiline-comment-indent ()
+  "Return the appropriate indent inside multiline comment.
+
+Assumes point is at beginning of line within comment. This
+function has basic logic to indent as you add new lines to a
+multiline comment, and to line up all the `*' if each line starts
+with `*'. The gofmt behavior for multiline comments is
+surprisingly complex and strange/buggy, so we just aim to do
+something simple rather than encode all the subtle behavior."
+  (let* (;; Indent of current line.
+         (indent (current-indentation))
+         ;; Indent of opening "/*".
+         start-indent
+         ;; Default indent to use based on preceding context.
+         natural-indent
+         ;; Non-nil means keep existing indent and give up calculating indent.
+         give-up
+         ;; Whether all comment lines (except first) begin with "*".
+         (all-star t))
+
+    (save-excursion
+      (go-goto-beginning-of-string-or-comment)
+
+      (setq start-indent (current-indentation))
+
+      ;; If other stuff precedes start of multiline comment, give up.
+      (setq give-up (/= (current-column) start-indent))
+
+      ;; Skip "/*".
+      (forward-char 2)
+
+      (skip-syntax-forward " ")
+
+      (if (not (eolp))
+          ;; If we aren't at EOL, we have content on the first line.
+          ;; Base our natural indent on that.
+          (setq natural-indent (current-column))
+        ;; Otherwise default to 1 space beyond "/*".
+        (setq natural-indent (+ start-indent 3)))
+
+      (let (done)
+        (while (not done)
+          (setq done (or (looking-at ".*\\*/") (not (zerop (forward-line)))))
+          (setq all-star (and all-star (looking-at "[[:space:]]*\\*"))))))
+
+    ;; If previous line has comment content, use its indent as our
+    ;; natural indent.
+    (save-excursion
+      (when (zerop (forward-line -1))
+        (beginning-of-line)
+        (when (and (go-in-comment-p) (> (current-indentation) 0))
+          (setq natural-indent (current-indentation)))))
+
+    (cond
+     (give-up indent)
+
+     (all-star (1+ start-indent))
+
+     ;; Closing "*/" with no preceding content always lines up with "/*".
+     ((looking-at "[[:space:]]*\\*/") start-indent)
+
+     ;; If the line is already indented, leave it.
+     (t (if (zerop indent) natural-indent indent)))))
+
+(defun go--indentation-at-point ()
+  "Return the appropriate indentation for the current non-comment line.
+
+This function works by walking a line's characters backwards. When it
+encounters a closing paren or brace it bounces to the corresponding
+opener. If it arrives at the beginning of the line you are indenting,
+it moves to the end of the previous line if the current line is a
+continuation line, else it moves to the containing opening paren or
+brace. If it arrives at the beginning of a line other than the line
+you are indenting, it will continue to the previous dangling line if
+the line you are indenting was not a continuation line, otherwise it
+is done."
+  (save-excursion
+    (beginning-of-line)
+
+    (let (
+          ;; Beginning of our starting line.
+          (start-line (point))
+
+          ;; Whether this is our first iteration of the outer while loop.
+          (first t)
+
+          ;; Whether we start in a block (i.e. our first line is not a
+          ;; continuation line and is in an "if", "for", etc. block).
+          (in-block)
+
+          ;; Our desired indent relative to our ending line's indent.
+          (indent 0))
+
+      ;; Skip leading whitespace.
+      (skip-syntax-forward " ")
+
+      ;; Decrement indent if the first character on the line is a closer.
+      (when (or (eq (char-after) ?\)) (eq (char-after) ?}))
+        (cl-decf indent tab-width))
+
+      (while (or
+              ;; Always run the first iteration so we process empty lines.
+              first
+
+              ;; Otherwise stop if we are at the start of a line.
+              (not (bolp)))
+        (setq first nil)
+
+        (cl-case (char-before)
+
+          ;; We have found a closer (paren or brace).
+          ((?\) ?})
+           (backward-char)
+           (let ((bol (line-beginning-position)))
+
+             ;; Jump back to corresponding opener.
+             (go-goto-opening-parenthesis)
+
+             ;; Here we decrement the indent if we are closing an indented
+             ;; expression. In other words, the closer's line was indented
+             ;; relative to the opener's line, and that indent should not
+             ;; be inherited by our starting line.
+             (when (and
+                    ;; We care about dangling expressions, not child blocks.
+                    (not in-block)
+
+                    ;; Opener and closer aren't on same line.
+                    (< (point) bol)
+
+                    (go-previous-line-has-dangling-op-p)
+
+                    ;; Opener is at same paren level as start of line (ignore sub-expressions).
+                    (eq (go-paren-level) (save-excursion (beginning-of-line) (go-paren-level)))
+
+                    ;; This dangling line opened indent relative to previous dangling line.
+                    (go--continuation-line-indents-p))
+               (cl-decf indent tab-width))))
+
+          ;; Brackets don't affect indentation, so just skip them.
+          ((?\])
+           (backward-char)))
+
+        ;; Skip non-closers since we are only interested in closing parens/braces.
+        (skip-syntax-backward "^)" (line-beginning-position))
+
+        (when (go-in-string-or-comment-p)
+          (go-goto-beginning-of-string-or-comment))
+
+        ;; At the beginning of the starting line.
+        (when (= start-line (point))
+
+          ;; We are a continuation line.
+          (if (go-previous-line-has-dangling-op-p)
+              (progn
+                ;; Presume a continuation line always gets an extra indent.
+                ;; We reduce the indent after the loop, if necessary.
+                (cl-incf indent tab-width)
+
+                ;; Go to the end of the dangling line.
+                (goto-char (go-previous-line-has-dangling-op-p)))
+
+            ;; If we aren't a continuation line and we have an enclosing paren
+            ;; or brace, jump to opener and increment our indent.
+            (when (go-goto-opening-parenthesis)
+              (setq in-block (go--flow-block-p))
+              (cl-incf indent tab-width))))
+
+        ;; If we started in a child block we must follow dangling lines
+        ;; until they don't dangle anymore. This is to handle cases like:
+        ;;
+        ;; if foo ||
+        ;;      foo &&
+        ;;        foo {
+        ;;   X
+        ;;
+        ;; There can be an arbitrary number of indents, so we must go back to
+        ;; the "if" to determine the indent of "X".
+        (when (and in-block (bolp) (go-previous-line-has-dangling-op-p))
+          (goto-char (go-previous-line-has-dangling-op-p))))
+
+      ;; If our ending line is a continuation line but doesn't open
+      ;; an extra indent, reduce indent. We tentatively gave indents to all
+      ;; dangling lines and all lines inside open parens, so here we take that
+      ;; indent back.
+      ;;
+      ;;                1 +                      1 +
+      ;; ending line      1 + foo(                 1 + foo(
+      ;; starting line      1,        becomes      1,
+      ;;                  )                      )
+      ;;
+      ;;
+      ;;                1 +                     1 +
+      ;; ending line      1 +         becomes     1 +
+      ;; starting line      1                     1
+      (when (and
+             (go-previous-line-has-dangling-op-p)
+             (not (go--continuation-line-indents-p)))
+        (cl-decf indent tab-width))
+
+      ;; Apply our computed indent relative to the indent of the
+      ;; ending line, or 0 if we are at the top level.
+      (if (and
+           (= 0 (go-paren-level))
+           (not (go-previous-line-has-dangling-op-p)))
+          indent
+        (+ indent (current-indentation))))))
+
+(defconst go--operator-chars "*/%<>&\\^+\\-|=!,."
+  "Individual characters that appear in operators.
+Comma and period are included because they can be dangling operators, so
+they need to be considered by `go--continuation-line-indents-p'")
+
+(defun go--operator-precedence (op)
+  "Go operator precedence (higher binds tighter)."
+  (cl-case (intern op)
+    (\. 7) ; "." in "foo.bar", binds tightest
+    (! 6)
+    ((* / % << >> & &^) 5)
+    ((+ - | ^) 4)
+    ((== != < <= > >=) 3)
+    (&& 2)
+    (|| 1)
+    (t 0)))
+
+(defun go--flow-block-p ()
+  "Return whether looking at a { that opens a control flow block.
+
+We check for a { that is preceded by a space and is not a func
+literal opening brace."
+  (save-excursion
+    (when (and
+           (eq (char-after) ?{)
+           (not (zerop (skip-syntax-backward " "))))
+
+      (let ((eol (line-end-position))
+            (level (go-paren-level))
+            (found-func-literal))
+
+        (beginning-of-line)
+
+        ;; See if we find any "func" keywords on this line at the same paren
+        ;; level as the curly.
+        (while (and
+                (not found-func-literal)
+                (re-search-forward "\\_<func\\_>" eol t))
+          (setq found-func-literal (and
+                                    (= level (go-paren-level))
+                                    (not (go-in-string-or-comment-p)))))
+        (not found-func-literal)))))
+
+(defun go--continuation-line-indents-p ()
+  "Return non-nil if the current continuation line opens an additional indent.
+
+This function works by looking at the Go operators used on the current
+line. If all the operators bind tighter than the previous line's
+dangling operator and the current line ends in a dangling operator or
+open paren, the next line will have an additional indent.
+
+For example:
+foo ||
+  foo && // this continuation line opens another indent
+    foo"
+  (save-excursion
+    (let (prev-op (all-tighter t))
+
+      ;; Record the dangling operator from previous line.
+      (save-excursion
+        (goto-char (go-previous-line-has-dangling-op-p))
+        (go--end-of-line)
+        (skip-syntax-backward " ")
+        (let ((end (point)))
+          (skip-chars-backward go--operator-chars)
+          (setq prev-op (buffer-substring-no-properties (point) end))))
+
+      (beginning-of-line)
+
+      (when (or
+             ;; We can only open indent if we have a dangling operator, or
+             (go--current-line-has-dangling-op-p)
+
+             (save-excursion
+               (go--end-of-line)
+               (backward-char)
+               (or
+                ;; Line ends in a "(" or ",", or
+                (eq (char-after) ?\()
+                (eq (char-after) ?,)
+
+                ;; Line ends in a "{" that isn't a control block.
+                (and
+                 (eq (char-after) ?{)
+                 (not (go--flow-block-p))))))
+
+        (let ((prev-precedence (go--operator-precedence prev-op))
+              (start-depth (go-paren-level))
+              (line-start (line-beginning-position)))
+
+          (end-of-line)
+
+          ;; While we haven't found a looser operator and are on the starting line...
+          (while (and all-tighter (> (point) line-start))
+
+            ;; Skip over non-operator characters.
+            (skip-chars-backward (concat "^" go--operator-chars) line-start)
+
+            (let ((end (point)))
+              (cond
+               ;; Ignore sub-expressions at different paren levels.
+               ((/= (go-paren-level) start-depth)
+                (skip-syntax-backward "^()"))
+
+               ((go-in-string-or-comment-p)
+                (go-goto-beginning-of-string-or-comment))
+
+               ;; We found an operator. Check if it has lower precedence.
+               ((/= (skip-chars-backward go--operator-chars) 0)
+                (when (>=
+                       prev-precedence
+                       (go--operator-precedence (buffer-substring (point) end)))
+                  (setq all-tighter nil)))))))
+        all-tighter))))
+
+(defun go--end-of-line ()
+  "Move to the end of the code on the current line.
+Point will be left before any trailing comments. Point will be left
+after the opening backtick of multiline strings."
+  (end-of-line)
+  (let ((keep-going t))
+    (while keep-going
+      (skip-syntax-backward " ")
+      (when (looking-back "\\*/" (- (point) 2))
+        ;; back up so we are in the /* comment */
+        (backward-char))
+      (if (go-in-comment-p)
+          (go-goto-beginning-of-string-or-comment)
+        (setq keep-going nil))))
+  (when (go-in-string-p)
+    (go-goto-beginning-of-string-or-comment)
+    ;; forward one so point is after the opening "`"
+    (forward-char)))
+
+(defun go--line-suffix-p (re)
+  "Return non-nil if RE matches the end of the line starting from `point'.
+
+Trailing whitespace, trailing comments and trailing multiline strings are
+ignored."
+  (let ((start (point))
+        (end (save-excursion (go--end-of-line) (point))))
+    (when (< start end)
+      (string-match-p
+       (concat "\\(?:" re "\\)$")
+       (buffer-substring-no-properties start end)))))
+
+(defun go--boring-line-p ()
+  "Return non-nil if the current line probably doesn't impact indentation.
+
+A boring line is one that starts with a comment, is empty, is part of a
+multiline comment, or starts and ends in a multiline string."
+  (or
+   (looking-at (concat go--comment-start-regexp "\\|[[:space:]]*$"))
+   (go-in-comment-p)
+   (and (go-in-string-p) (save-excursion (end-of-line) (go-in-string-p)))))
+
+(defun go--forward-line (&optional count)
+  "Like `forward-line' but skip comments and empty lines.
+
+Return non-nil if point changed lines."
+  (let (moved)
+    (while (and
+            (zerop (forward-line count))
+            (setq moved t)
+            (go--boring-line-p))
+      (setq count (if (and count (< count 0 )) -1 1)))
+    moved))
+
+(defun go--case-comment-p (indent)
+  "Return non-nil if looking at a comment attached to a case statement.
+
+INDENT is the normal indent of this line, i.e. that of the case body."
+  (when (and
+         (> (current-indentation) 0)
+         (looking-at go--comment-start-regexp))
+
+    (let (switch-before
+          case-after
+          has-case-aligned-preceding-comment)
+
+      (save-excursion
+        ;; Search for previous case-aligned comment.
+        (while (and
+                (zerop (forward-line -1))
+                (cond
+                 ((looking-at "^[[:space:]]*$"))
+
+                 ((looking-at go--comment-start-regexp)
+                  (when (= (current-indentation) (- indent tab-width))
+                    (setq has-case-aligned-preceding-comment t))
+                  t)
+
+                 ((go-in-comment-p)))))
+
+        ;; Record if a switch (or select) precedes us.
+        (setq switch-before (looking-at "^[[:space:]]*\\(switch\\|select\\)[[:space:]]")))
+
+      ;; Record if first proceeding non-comment line is a case statement.
+      (save-excursion
+        (while (and
+                (zerop (forward-line 1))
+                (or
+                 (looking-at go--comment-start-regexp)
+                 (looking-at "^[[:space:]]*$")
+                 (go-in-comment-p))))
+
+        (setq case-after (looking-at go--case-or-default-regexp)))
+
+      (and
+       ;; a "case" statement comes after our comment
+       case-after
+
+       (or
+        ;; "switch" statement precedes us, always align with "case"
+        switch-before
+
+        ;; a preceding comment is aligned with "case", we should too
+        has-case-aligned-preceding-comment
+
+        ;; other cases are ambiguous, so if comment is currently
+        ;; aligned with "case", leave it that way
+        (= (current-indentation) (- indent tab-width)))))))
+
+(defun go-mode-indent-line ()
+  (interactive)
+  (let (indent
+        ;; case sensitively match "case", "default", etc.
+        (case-fold-search nil)
+        (pos (- (point-max) (point)))
+        (point (point))
+        (beg (line-beginning-position))
+        (non-tab-indents 0))
+    (back-to-indentation)
+    (if (go-in-string-p)
+        (goto-char point)
+      (setq indent (go-indentation-at-point))
+      (when (or
+             (and
+              (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|" go--case-or-default-regexp))
+              ;; don't think last part of multiline case statement is a label
+              (not (go-previous-line-has-dangling-op-p))
+              (not (go--in-case-clause-list-p))
+              (not (go--in-composite-literal-p)))
+
+             ;; comment attached above a "case" statement
+             (go--case-comment-p indent))
+        (cl-decf indent tab-width))
+
+      ;; Don't do anything if current indent is correct.
+      (when (/= indent (current-column))
+        ;; Don't use tabs for indenting beyond "/*" in multiline
+        ;; comments. They don't play well with gofmt.
+        (when (go-in-comment-p)
+          (save-excursion
+            (go-goto-beginning-of-string-or-comment)
+            (when (> indent (current-indentation))
+              (setq non-tab-indents (- indent (current-indentation)))
+              (setq indent (current-indentation)))))
+
+        (delete-region beg (point))
+        (indent-to indent)
+        (insert-char ?  non-tab-indents))
+
+      ;; If initial point was within line's indentation,
+      ;; position after the indentation.  Else stay at same point in text.
+      (if (> (- (point-max) pos) (point))
+          (goto-char (- (point-max) pos))))))
+
+(defun go-beginning-of-defun (&optional count)
+  (when (and (not (go-in-string-or-comment-p))
+                        (not (bolp))
+                        (save-excursion
+                          (beginning-of-line)
+                          (looking-at go-func-meth-regexp)))
+       ;; Point is already somewhere on the function definition. Move to the end of line so that searching backwards finds
+       ;; it. We don't go to the end of line unconditionally because that confuses evil-mode
+       ;; (https://github.com/dominikh/go-mode.el/issues/186)
+       ;;
+       ;; If point is already at the beginning of line and looking at a function, then we want go-beginning-of-defun to
+       ;; jump to the previous function instead.
+       (end-of-line))
+  (setq count (or count 1))
+  (let (first failure)
+    (dotimes (i (abs count))
+      (setq first t)
+      (while (and (not failure)
+                  (or first (go-in-string-or-comment-p)))
+        (if (>= count 0)
+            (progn
+              (go--backward-irrelevant)
+              (if (not (re-search-backward go-func-meth-regexp nil t))
+                  (setq failure t)))
+          (if (looking-at go-func-meth-regexp)
+              (forward-char))
+          (if (not (re-search-forward go-func-meth-regexp nil t))
+              (setq failure t)))
+        (setq first nil)))
+    (if (< count 0)
+        (beginning-of-line))
+    (not failure)))
+
+(defun go-end-of-defun ()
+  (let (orig-level)
+    ;; It can happen that we're not placed before a function by emacs
+    (if (not (looking-at "func"))
+        (go-beginning-of-defun -1))
+    ;; Find the { that starts the function, i.e., the next { that isn't
+    ;; preceded by struct or interface, or a comment or struct tag.  BUG:
+    ;; breaks if there's a comment between the struct/interface keyword and
+    ;; bracket, like this:
+    ;;
+    ;;     struct /* why? */ {
+    (while (progn
+      (skip-chars-forward "^{")
+      (forward-char)
+      (or (go-in-string-or-comment-p)
+          (looking-back "\\(struct\\|interface\\)\\s-*{"
+                        (line-beginning-position)))))
+    (setq orig-level (go-paren-level))
+    (while (>= (go-paren-level) orig-level)
+      (skip-chars-forward "^}")
+      (forward-char))))
+
+
+(defvar 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
+  "Position of \"(\" starting param list.
+
+This is used during fontification of function signatures.")
+
+(defun go--fontify-param-pre ()
+  "Set `go--fontify-param-has-name' and `go--fontify-param-beg' appropriately.
+
+This is used as an anchored font lock keyword PRE-MATCH-FORM. We
+must set `go--fontify-param-has-name' ahead of time because you
+can't know if the param list is types only or names and types
+until you see the end. For example:
+
+// types only
+func foo(int, string) {}
+
+// names and types (don't know so until you see the \"int\").
+func foo(i, j int) {}"
+  (setq go--fontify-param-has-name (eq
+                                    (go--parameter-list-type (point-max))
+                                    'present))
+
+  ;; Remember where our match started so we can continue our search
+  ;; from here.
+  (setq go--fontify-param-beg (point))
+
+  ;; Return position of closing paren so we process the entire
+  ;; multiline param list.
+  (save-excursion
+    (let ((depth (go-paren-level)))
+      ;; First check that our paren is closed by the end of the file. This
+      ;; avoids expanding the fontification region to the entire file when you
+      ;; have an unclosed paren at file scope.
+      (when (save-excursion
+              (goto-char (1+ (buffer-size)))
+              (< (go-paren-level) depth))
+        (while (and
+                (re-search-forward ")" nil t)
+                (>= (go-paren-level) depth)))))
+    (point)))
+
+(defun go--fontify-param-post ()
+  "Move point back to opening paren.
+
+This is used as an anchored font lock keyword POST-MATCH-FORM. We
+move point back to the opening \"(\" so we find nested param
+lists."
+  (goto-char go--fontify-param-beg))
+
+(defun go--match-param-start (end)
+  "Search for the starting of param lists.
+
+Search for the opening `(' of function signature param lists.
+This covers the func receiver, params, and results. Interface
+declarations are also included."
+  (let (found-match)
+    (while (and
+            (not found-match)
+            (re-search-forward (concat "\\(\\_<" go-identifier-regexp "\\)?(") 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)))))))))
+    found-match))
+
+
+(defconst go--named-param-re
+  (concat "[[:space:]\n]*\\(" go-identifier-regexp "\\)\\(?:[[:space:]]+\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)]\\)?")
+  "Regexp to match named param such as \"s *string\" in:
+
+func(i int, s *string) { }")
+
+(defconst go--unnamed-param-re
+  (concat "\\(\\)[[:space:]\n]*\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)]")
+  "Regexp to match unnamed param such as \"*string\" in:
+
+func(int, *string) { }
+
+We start with an empty subexp since our font lock keyword expects
+subexp 1 to a variable name, but we have no variable.")
+
+(defun go--fontify-param (end)
+  "Match a param within a param list.
+
+Our parent font lock matcher is anchored to the beginning of the
+param list. `go--fontify-param-has-name' has been set
+appropriately. We match the next param and advance point to after
+the next comma or to the closing paren."
+  (let (found-match done)
+    ;; We loop until match because there are some params that we can't
+    ;; handle (but we may need to handle subsequent params). For
+    ;; example:
+    ;;
+    ;; // We don't handle the interface, so we must skip it and handle
+    ;; // "string".
+    ;; func(int, interface { foo() }, string)
+    (while (and (not found-match) (not done))
+      (if go--fontify-param-has-name
+          (when (looking-at go--named-param-re)
+            (when (not go-fontify-variables)
+              (let ((md (match-data)))
+                (setf (nth 2 md) nil (nth 3 md) nil)
+                (set-match-data md)))
+            (setq found-match t))
+        (when (looking-at go--unnamed-param-re)
+          (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))))
+    found-match))
+
+(defun go--search-next-comma (end)
+  "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 "[^,)]")
+                    (> (go-paren-level) orig-level)))
+      (forward-char))
+    (when (and (looking-at-p ",")
+               (< (point) (1- end)))
+      (forward-char)
+      t)))
+
+(defun go--looking-at-keyword ()
+  (and (looking-at (concat "\\(" go-identifier-regexp "\\)"))
+       (member (match-string 1) go-mode-keywords)))
+
+(defun go--match-type-switch-case (end)
+  "Match a \"case\" clause within a type switch."
+  (let (found-match)
+    (while (and
+            (not found-match)
+
+            ;; Search for "case" statements.
+            (re-search-forward "^[[:space:]]*case " end t))
+
+      ;; Make sure we are in a type switch statement.
+      (setq found-match (go--in-type-switch-p)))
+    found-match))
+
+(defun go--fontify-type-switch-case (end)
+  "Match a single type within a type switch case."
+  (let (found-match done)
+    ;; Loop until we find a match because we must skip types we don't
+    ;; handle, such as "interface { foo() }".
+    (while (and (not found-match) (not done))
+      (when (looking-at (concat "\\(?:[[:space:]]*\\|//.*\\|\n\\)*" go-type-name-regexp "[[:space:]]*[,:]"))
+        (goto-char (match-end 1))
+        (unless (member (match-string 1) go-constants)
+          (setq found-match t)))
+      (setq done (not (go--search-next-comma end))))
+    found-match))
+
+(defun go--containing-decl ()
+  "Return containing decl kind var|const|type, if any."
+  (save-match-data
+    (or
+     (save-excursion
+       (and
+        (go-goto-opening-parenthesis)
+        (eq (char-after) ?\()
+        (skip-syntax-backward " ")
+        (skip-syntax-backward "w")
+        (looking-at "\\(var\\|const\\|type\\)[[:space:]]")
+        (match-string-no-properties 1)))
+
+     (save-excursion
+       (let ((depth (go-paren-level)))
+         (beginning-of-line)
+         (and
+          (= (go-paren-level) depth)
+          (looking-at "[[:space:]]*\\(var\\|const\\|type\\)[[:space:]]")
+          (match-string-no-properties 1)))))))
+
+(defconst go--decl-ident-re (concat "\\(?:^\\|[[:space:]]\\)\\(\\(\\(" go-identifier-regexp "\\)\\)\\)\\_>"))
+
+(defun go--match-decl (end)
+  "Match identifiers in \"var\", \"type\" and \"const\" decls, as
+well as \":=\" assignments.
+
+In order to only scan once, the regex has three subexpressions
+that match the same identifier. Depending on the kind of
+containing decl we zero out the subexpressions so the right one
+gets highlighted by the font lock keyword."
+  (let (found-match decl)
+    (while (and
+            (not found-match)
+            (re-search-forward go--decl-ident-re end t))
+
+      (save-excursion
+        ;; Skip keywords.
+        (cond
+         ((member (match-string 1) go-mode-keywords))
+
+         ((and
+           ;; We are in a decl of some kind.
+           (setq decl (go--containing-decl))
+
+           ;; We aren't on right side of equals sign.
+           (not (go--looking-back-p "=")))
+
+          (setq found-match t)
+
+          ;; Unset match data subexpressions that don't apply based on
+          ;; the decl kind.
+          (let ((md (match-data)))
+            (cond
+             ((string= decl "var")
+              (setf (nth 4 md) nil (nth 5 md) nil (nth 6 md) nil (nth 7 md) nil)
+              (when (not go-fontify-variables)
+                (setf (nth 2 md) nil (nth 3 md) nil)))
+             ((string= decl "const")
+              (setf (nth 2 md) nil (nth 3 md) nil (nth 6 md) nil (nth 7 md) nil))
+             ((string= decl "type")
+              (setf (nth 2 md) nil (nth 3 md) nil (nth 4 md) nil (nth 5 md) nil)))
+            (set-match-data md)))
+
+         (go-fontify-variables
+          (save-match-data
+            ;; Left side of ":=" assignment.
+            (when (looking-at ".*:=")
+              (let ((depth (go-paren-level)))
+                (goto-char (match-end 0))
+                ;; Make sure the ":=" isn't in a comment or a sub-block.
+                (setq found-match (and
+                                   (not (go-in-string-or-comment-p))
+                                   (= depth (go-paren-level)))))))))))
+    found-match))
+
+(defun go--looking-back-p (re)
+  "Return non-nil if RE matches beginning of line to point.
+
+RE is not anchored automatically."
+  (string-match-p
+   re
+   (buffer-substring-no-properties (point) (line-beginning-position))))
+
+
+(defconst go--ident-type-pair-re (concat "\\_<\\(" go-identifier-regexp "\\)[[:space:]]+" go-type-name-regexp))
+
+(defun go--match-ident-type-pair (end)
+  "Search for identifier + type-name pairs.
+
+For example, this looks for the \"foo bar\" in \"var foo bar\",
+yielding match-data for \"bar\" since that is a type name to be
+fontified. This approach matches type names in var and const
+decls, and in struct definitions. Return non-nil if search
+succeeds."
+  (let (found-match)
+    (while (and
+            (not found-match)
+            (re-search-forward go--ident-type-pair-re end t))
+
+      ;; Make sure the neither match is a keyword.
+      (if (member (match-string 2) go-mode-keywords)
+          (goto-char (match-end 2))
+        (if (member (match-string 1) go-mode-keywords)
+            (goto-char (match-end 1))
+          (setq found-match t))))
+
+    found-match))
+
+(defconst go--single-func-result-re (concat ")[[:space:]]+" go-type-name-regexp "\\(?:$\\|[[:space:]),]\\)"))
+
+(defun go--match-single-func-result (end)
+  "Match single result types.
+
+Parenthetical result lists are handled by the param list keyword,
+so we need a separate keyword to handle singular result types
+such as \"string\" in:
+
+func foo(i int) string"
+  (let (found-match)
+    (while (and
+            (not found-match)
+            (re-search-forward go--single-func-result-re end t))
+      (when (not (member (match-string 1) go-mode-keywords))
+        (setq found-match t)
+        (goto-char (match-end 1))))
+    found-match))
+
+(defconst go--type-alias-re
+  (concat "^[[:space:]]*\\(type[[:space:]]+\\)?" go-identifier-regexp "[[:space:]]*=[[:space:]]*" go-type-name-regexp))
+
+(defun go--match-type-alias (end)
+  "Search for type aliases.
+
+We are looking for the right-hand-side of the type alias"
+  (let (found-match)
+    (while (and
+            (not found-match)
+            (re-search-forward go--type-alias-re end t))
+      ;; Either line started with "type", or we are in a "type" block.
+      (setq found-match (or
+                         (match-string 1)
+                         (go--in-paren-with-prefix-p ?\( "type"))))
+    found-match))
+
+
+(defconst go--map-value-re
+  (concat "\\_<map\\_>\\[\\(?:\\[[^]]*\\]\\)*[^]]*\\]" 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))
+
+(defconst go--label-re (concat "\\(" go-label-regexp "\\):"))
+
+(defun go--match-ident-colon (end)
+  "Search for composite literal field names and label definitions."
+  (let (found-match)
+    (while (and
+            (not found-match)
+            (re-search-forward go--label-re end t))
+
+      (save-excursion
+        (goto-char (match-beginning 1))
+        (skip-syntax-backward " ")
+
+        (setq found-match (or
+                           ;; We are a label/field name if we are at the
+                           ;; beginning of the line.
+                           (bolp)
+
+                           ;; Composite literal field names, e.g. "Foo{Bar:". Note
+                           ;; that this gives false positives for literal maps,
+                           ;; arrays, and slices.
+                           (and
+                            (or (eq (char-before) ?,) (eq (char-before) ?{))
+                            (go--in-composite-literal-p))))))
+
+    found-match))
+
+(defun go--parameter-list-type (end)
+  "Return `present' if the parameter list has names, or `absent' if not.
+Assumes 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))))
+
+(defun go--reset-dangling-cache-before-change (&optional _beg _end)
+  "Reset `go-dangling-cache'.
+
+This is intended to be called from `before-change-functions'."
+  (setq go-dangling-cache (make-hash-table :test 'eql)))
+
+(defun go--electric-indent-function (inserted-char)
+  (let ((prev (char-before (1- (point)))))
+    (cond
+     ;; Indent after starting/ending a comment. This is handy for
+     ;; comments above "case" statements and closing multiline
+     ;; comments.
+     ((or
+       (and (eq inserted-char ?/) (eq prev ?/))
+       (and (eq inserted-char ?/) (eq prev ?*))
+       (and (eq inserted-char ?*) (eq prev ?/)))
+      'do-indent)
+
+     ((eq inserted-char ? )
+      (and
+       (eq prev ?e)
+       (eq (char-before (- (point) 2)) ?s)
+       (eq (char-before (- (point) 3)) ?a)
+       (eq (char-before (- (point) 4)) ?c)))
+
+     ;; Trick electric-indent-mode into indenting inside multiline
+     ;; comments.
+     ((and (eq inserted-char ?\n) (go-in-comment-p))
+      'do-indent))))
+
+(defun go--comment-region (beg end &optional arg)
+  "Switch to block comment when commenting a partial line."
+  (save-excursion
+    (goto-char beg)
+    (let ((beg-bol (line-beginning-position)))
+      (goto-char end)
+      (if (and
+           ;; beg and end are on the same line
+           (eq (line-beginning-position) beg-bol)
+           ;; end is not at end of line
+           (not (eq end (line-end-position))))
+          (let ((comment-start "/* ")
+                (comment-end " */")
+                (comment-padding ""))
+            (comment-region-default beg end arg))
+        (comment-region-default beg end arg)))))
+
+;;;###autoload
+(define-derived-mode go-mode prog-mode "Go"
+  "Major mode for editing Go source text.
+
+This mode provides (not just) basic editing capabilities for
+working with Go code. It offers almost complete syntax
+highlighting, indentation that is almost identical to gofmt and
+proper parsing of the buffer content to allow features such as
+navigation by function, manipulation of comments or detection of
+strings.
+
+In addition to these core features, it offers various features to
+help with writing Go code. You can directly run buffer content
+through gofmt, read godoc documentation from within Emacs, modify
+and clean up the list of package imports or interact with the
+Playground (uploading and downloading pastes).
+
+The following extra functions are defined:
+
+- `gofmt'
+- `godoc' and `godoc-at-point'
+- `go-import-add'
+- `go-goto-arguments'
+- `go-goto-docstring'
+- `go-goto-function'
+- `go-goto-function-name'
+- `go-goto-imports'
+- `go-goto-return-values'
+- `go-goto-method-receiver'
+- `go-play-buffer' and `go-play-region'
+- `go-download-play'
+- `godef-describe' and `godef-jump'
+- `go-coverage'
+
+If you want to automatically run `gofmt' before saving a file,
+add the following hook to your Emacs configuration:
+
+\(add-hook 'before-save-hook #'gofmt-before-save)
+
+If you want to use `godef-jump' instead of etags (or similar),
+consider binding godef-jump to `M-.', which is the default key
+for `find-tag':
+
+\(add-hook 'go-mode-hook (lambda ()
+                          (local-set-key (kbd \"M-.\") #'godef-jump)))
+
+Please note that godef is an external dependency. You can install
+it with
+
+go get github.com/rogpeppe/godef
+
+
+If you're looking for even more integration with Go, namely
+on-the-fly syntax checking, auto-completion and snippets, it is
+recommended that you look at flycheck
+\(see URL `https://github.com/flycheck/flycheck') or flymake in combination
+with goflymake (see URL `https://github.com/dougm/goflymake'), gocode
+\(see URL `https://github.com/nsf/gocode'), go-eldoc
+\(see URL `github.com/syohex/emacs-go-eldoc') and yasnippet-go
+\(see URL `https://github.com/dominikh/yasnippet-go')"
+
+  ;; Font lock
+  (setq font-lock-defaults '(go--build-font-lock-keywords))
+  (setq font-lock-multiline t)
+
+  ;; Indentation
+  (set (make-local-variable 'indent-line-function) #'go-mode-indent-line)
+
+  ;; Comments
+  (set (make-local-variable 'comment-start) "// ")
+  (set (make-local-variable 'comment-end)   "")
+  (set (make-local-variable 'comment-use-syntax) t)
+  (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
+  (set (make-local-variable 'comment-region-function) #'go--comment-region)
+  ;; Set comment-multi-line to t so that comment-indent-new-line
+  ;; doesn't use one /* */ per line. Thanks to comment-use-syntax,
+  ;; Emacs is smart enough to still insert new // for single-line
+  ;; comments.
+  (set (make-local-variable 'comment-multi-line) t)
+
+  (set (make-local-variable 'beginning-of-defun-function) #'go-beginning-of-defun)
+  (set (make-local-variable 'end-of-defun-function) #'go-end-of-defun)
+  (setq-local paragraph-start
+              (concat "[[:space:]]*\\(?:"
+                      comment-start-skip
+                      "\\|\\*/?[[:space:]]*\\|\\)$"))
+  (setq-local paragraph-separate paragraph-start)
+  (setq-local fill-paragraph-function #'go-fill-paragraph)
+  (setq-local fill-forward-paragraph-function #'go--fill-forward-paragraph)
+  (setq-local adaptive-fill-function #'go--find-fill-prefix)
+  (setq-local adaptive-fill-first-line-regexp "")
+  (setq-local comment-line-break-function #'go--comment-indent-new-line)
+
+  (set (make-local-variable 'parse-sexp-lookup-properties) t)
+  (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax)
+
+  (when (boundp 'electric-indent-chars)
+    (set (make-local-variable 'electric-indent-chars) '(?\n ?} ?\) ?:))
+    (add-hook 'electric-indent-functions #'go--electric-indent-function nil t))
+
+  (set (make-local-variable 'compilation-error-screen-columns) nil)
+
+  (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
+  (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t)
+
+  ;; ff-find-other-file
+  (setq ff-other-file-alist 'go-other-file-alist)
+
+  (setq imenu-generic-expression
+        '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
+          ("func" "^func *\\(.*\\) {" 1)))
+  (imenu-add-to-menubar "Index")
+
+  ;; Go style
+  (setq indent-tabs-mode t)
+
+  ;; Handle unit test failure output in compilation-mode
+  ;;
+  ;; Note that we add our entry to the beginning of
+  ;; compilation-error-regexp-alist. In older versions of Emacs, the
+  ;; list was processed from the end, and we would've wanted to add
+  ;; ours last. But at some point this changed, and now the list is
+  ;; processed from the beginning. It's important that our entry comes
+  ;; before gnu, because gnu matches go test output, but includes the
+  ;; leading whitespace in the file name.
+  ;;
+  ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html
+  ;; documents the old, reversed order.
+  (when (and (boundp 'compilation-error-regexp-alist)
+             (boundp 'compilation-error-regexp-alist-alist))
+    (add-to-list 'compilation-error-regexp-alist 'go-test)
+    (add-to-list 'compilation-error-regexp-alist-alist
+                 '(go-test . ("^\\s-+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
+
+(defun go--apply-rcs-patch (patch-buffer)
+  "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."
+  (let ((target-buffer (current-buffer))
+        ;; Relative offset between buffer line numbers and line numbers
+        ;; in patch.
+        ;;
+        ;; Line numbers in the patch are based on the source file, so
+        ;; we have to keep an offset when making changes to the
+        ;; buffer.
+        ;;
+        ;; Appending lines decrements the offset (possibly making it
+        ;; negative), deleting lines increments it. This order
+        ;; simplifies the forward-line invocations.
+        (line-offset 0)
+        (column (current-column)))
+    (save-excursion
+      (with-current-buffer patch-buffer
+        (goto-char (point-min))
+        (while (not (eobp))
+          (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
+            (error "Invalid rcs patch or internal error in go--apply-rcs-patch"))
+          (forward-line)
+          (let ((action (match-string 1))
+                (from (string-to-number (match-string 2)))
+                (len  (string-to-number (match-string 3))))
+            (cond
+             ((equal action "a")
+              (let ((start (point)))
+                (forward-line len)
+                (let ((text (buffer-substring start (point))))
+                  (with-current-buffer target-buffer
+                    (cl-decf line-offset len)
+                    (goto-char (point-min))
+                    (forward-line (- from len line-offset))
+                    (insert text)))))
+             ((equal action "d")
+              (with-current-buffer target-buffer
+                (go--goto-line (- from line-offset))
+                (cl-incf line-offset len)
+                (go--delete-whole-line len)))
+             (t
+              (error "Invalid rcs patch or internal error in go--apply-rcs-patch")))))))
+    (move-to-column column)))
+
+(defun gofmt--is-goimports-p ()
+  (string-equal (file-name-base gofmt-command) "goimports"))
+
+(defun gofmt ()
+  "Format the current buffer according to the formatting tool.
+
+The tool used can be set via ‘gofmt-command’ (default: gofmt) and additional
+arguments can be set as a list via ‘gofmt-args’."
+  (interactive)
+  (let ((tmpfile (make-nearby-temp-file "gofmt" nil ".go"))
+        (patchbuf (get-buffer-create "*Gofmt patch*"))
+        (errbuf (if gofmt-show-errors (get-buffer-create "*Gofmt Errors*")))
+        (coding-system-for-read 'utf-8)
+        (coding-system-for-write 'utf-8)
+        our-gofmt-args)
+
+    (unwind-protect
+        (save-restriction
+          (widen)
+          (if errbuf
+              (with-current-buffer errbuf
+                (setq buffer-read-only nil)
+                (erase-buffer)))
+          (with-current-buffer patchbuf
+            (erase-buffer))
+
+          (write-region nil nil tmpfile)
+
+          (when (and (gofmt--is-goimports-p) buffer-file-name)
+            (setq our-gofmt-args
+                  (append our-gofmt-args
+                          ;; srcdir, despite its name, supports
+                          ;; accepting a full path, and some features
+                          ;; of goimports rely on knowing the full
+                          ;; name.
+                          (list "-srcdir" (file-local-name
+                                           (file-truename buffer-file-name))))))
+          (setq our-gofmt-args
+                (append our-gofmt-args gofmt-args
+                        (list "-w" (file-local-name tmpfile))))
+          (message "Calling gofmt: %s %s" gofmt-command our-gofmt-args)
+          ;; We're using errbuf for the mixed stdout and stderr output. This
+          ;; is not an issue because gofmt -w does not produce any stdout
+          ;; output in case of success.
+          (if (zerop (apply #'process-file gofmt-command nil errbuf nil our-gofmt-args))
+              (progn
+                ;; There is no remote variant of ‘call-process-region’, but we
+                ;; can invoke diff locally, and the results should be the same.
+                (if (zerop (let ((local-copy (file-local-copy tmpfile)))
+                             (unwind-protect
+                                 (call-process-region
+                                  (point-min) (point-max) "diff" nil patchbuf
+                                  nil "-n" "-" (or local-copy tmpfile))
+                               (when local-copy (delete-file local-copy)))))
+                    (message "Buffer is already gofmted")
+                  (go--apply-rcs-patch patchbuf)
+                  (message "Applied gofmt"))
+                (if errbuf (gofmt--kill-error-buffer errbuf)))
+            (message "Could not apply gofmt")
+            (if errbuf (gofmt--process-errors (buffer-file-name) tmpfile errbuf))))
+
+      (kill-buffer patchbuf)
+      (delete-file tmpfile))))
+
+
+(defun gofmt--process-errors (filename tmpfile errbuf)
+  (with-current-buffer errbuf
+    (if (eq gofmt-show-errors 'echo)
+        (progn
+          (message "%s" (buffer-string))
+          (gofmt--kill-error-buffer errbuf))
+      ;; Convert the gofmt stderr to something understood by the compilation mode.
+      (goto-char (point-min))
+      (if (save-excursion
+            (save-match-data
+              (search-forward "flag provided but not defined: -srcdir" nil t)))
+          (insert "Your version of goimports is too old and doesn't support vendoring. Please update goimports!\n\n"))
+      (insert "gofmt errors:\n")
+      (let ((truefile
+             (if (gofmt--is-goimports-p)
+                 (concat (file-name-directory filename) (file-name-nondirectory tmpfile))
+               tmpfile)))
+        (while (search-forward-regexp
+                (concat "^\\(" (regexp-quote (file-local-name truefile))
+                        "\\):")
+                nil t)
+          (replace-match (file-name-nondirectory filename) t t nil 1)))
+      (compilation-mode)
+      (display-buffer errbuf))))
+
+(defun gofmt--kill-error-buffer (errbuf)
+  (let ((win (get-buffer-window errbuf)))
+    (if win
+        (quit-window t win)
+      (kill-buffer errbuf))))
+
+;;;###autoload
+(defun gofmt-before-save ()
+  "Add this to .emacs to run gofmt on the current buffer when saving:
+\(add-hook 'before-save-hook 'gofmt-before-save).
+
+Note that this will cause ‘go-mode’ to get loaded the first time
+you save any file, kind of defeating the point of autoloading."
+
+  (interactive)
+  (when (eq major-mode 'go-mode) (gofmt)))
+
+(defun godoc--read-query ()
+  "Read a godoc query from the minibuffer."
+  (if godoc-use-completing-read
+      (completing-read "godoc; "
+                       (go-packages) nil nil nil 'go-godoc-history)
+    (read-from-minibuffer "godoc: " nil nil nil 'go-godoc-history)))
+
+(defun godoc--buffer-name (query)
+  "Determine the name to use for the output buffer of a given godoc QUERY."
+  (if godoc-reuse-buffer
+      "*godoc*"
+    (concat "*godoc " query "*")))
+
+(defun godoc--get-buffer (query)
+  "Get an empty buffer for a godoc QUERY."
+  (let* ((buffer-name (godoc--buffer-name query))
+         (buffer (get-buffer buffer-name)))
+    ;; Kill the existing buffer if it already exists.
+    (when buffer (kill-buffer buffer))
+    (get-buffer-create buffer-name)))
+
+(defun godoc--buffer-sentinel (proc event)
+  "Sentinel function run when godoc command completes."
+  (with-current-buffer (process-buffer proc)
+    (cond ((string= event "finished\n")  ;; Successful exit.
+           (goto-char (point-min))
+           (godoc-mode)
+           (display-buffer (current-buffer) t))
+          ((/= (process-exit-status proc) 0)  ;; Error exit.
+           (let ((output (buffer-string)))
+             (kill-buffer (current-buffer))
+             (message (concat "godoc: " output)))))))
+
+(define-derived-mode godoc-mode special-mode "Godoc"
+  "Major mode for showing Go documentation."
+  (view-mode-enter))
+
+;;;###autoload
+(defun godoc (query)
+  "Show Go documentation for QUERY, much like \\<go-mode-map>\\[man]."
+  (interactive (list (godoc--read-query)))
+  (go--godoc query godoc-command))
+
+(defun go--godoc (query command)
+  (unless (string= query "")
+    (set-process-sentinel
+     (start-process-shell-command "godoc" (godoc--get-buffer query)
+                                  (concat command " " query))
+     'godoc--buffer-sentinel)
+    nil))
+
+(defun godoc-at-point (point)
+  "Show Go documentation for the identifier at POINT.
+
+It uses `godoc-at-point-function' to look up the documentation."
+  (interactive "d")
+  (funcall godoc-at-point-function point))
+
+(defun go-goto-imports ()
+  "Move point to the block of imports.
+
+If using
+
+  import (
+    \"foo\"
+    \"bar\"
+  )
+
+it will move point directly behind the last import.
+
+If using
+
+  import \"foo\"
+  import \"bar\"
+
+it will move point to the next line after the last import.
+
+If no imports can be found, point will be moved after the package
+declaration."
+  (interactive)
+  ;; FIXME if there's a block-commented import before the real
+  ;; imports, we'll jump to that one.
+
+  ;; Generally, this function isn't very forgiving. it'll bark on
+  ;; extra whitespace. It works well for clean code.
+  (let ((old-point (point)))
+    (goto-char (point-min))
+    (cond
+     ((re-search-forward "^import ()" nil t)
+      (backward-char 1)
+      'block-empty)
+     ((re-search-forward "^import ([^)]+)" nil t)
+      (backward-char 2)
+      'block)
+     ((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t)
+      'single)
+     ((re-search-forward "^[[:space:]\n]*package .+?\n" nil t)
+      (message "No imports found, moving point after package declaration")
+      'none)
+     (t
+      (goto-char old-point)
+      (message "No imports or package declaration found. Is this really a Go file?")
+      'fail))))
+
+(defun go-play-buffer ()
+  "Like `go-play-region', but acts on the entire buffer."
+  (interactive)
+  (go-play-region (point-min) (point-max)))
+
+(defun go-play-region (start end)
+  "Send the region between START and END to the Playground.
+If non-nil `go-play-browse-function' is called with the
+Playground URL.
+
+By default this function will prompt to confirm you want to upload
+code to the Playground. You can disable the confirmation by setting
+`go-confirm-playground-uploads' to nil."
+  (interactive "r")
+  (if (and go-confirm-playground-uploads
+           (not (yes-or-no-p "Upload to public Go Playground? ")))
+      (message "Upload aborted")
+    (let* ((url-request-method "POST")
+           (url-request-extra-headers
+            '(("Content-Type" . "text/plain; charset=UTF-8")))
+           (url-request-data
+            (encode-coding-string
+             (buffer-substring-no-properties start end)
+             'utf-8))
+
+           (content-buf (url-retrieve
+                         "https://play.golang.org/share"
+                         (lambda (arg)
+                           (cond
+                            ((equal :error (car arg))
+                             (signal 'go-play-error (cdr arg)))
+                            (t
+                             (re-search-forward "\n\n")
+                             (let ((url (format "https://play.golang.org/p/%s"
+                                                (buffer-substring (point) (point-max)))))
+                               (when go-play-browse-function
+                                 (funcall go-play-browse-function url))))))))))))
+
+;;;###autoload
+(defun go-download-play (url)
+  "Download a paste from the playground and insert it in a Go buffer.
+Tries to look for a URL at point."
+  (interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap-string-at-point 'url)))))
+  (with-current-buffer
+      (let ((url-request-method "GET") url-request-data url-request-extra-headers)
+        (url-retrieve-synchronously (concat url ".go")))
+    (let ((buffer (generate-new-buffer (concat (car (last (split-string url "/"))) ".go"))))
+      (goto-char (point-min))
+      (re-search-forward "\n\n")
+      (copy-to-buffer buffer (point) (point-max))
+      (kill-buffer)
+      (with-current-buffer buffer
+        (go-mode)
+        (switch-to-buffer buffer)))))
+
+(defun go-propertize-syntax (start end)
+  (save-excursion
+    (goto-char start)
+    (while (search-forward "\\" end t)
+      (put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9))))))
+
+(defun go-import-add (arg import)
+  "Add a new IMPORT to the list of imports.
+
+When called with a prefix ARG asks for an alternative name to
+import the package as.
+
+If no list exists yet, one will be created if possible.
+
+If an identical import has been commented, it will be
+uncommented, otherwise a new import will be added."
+
+  ;; - If there's a matching `// import "foo"`, uncomment it
+  ;; - If we're in an import() block and there's a matching `"foo"`, uncomment it
+  ;; - Otherwise add a new import, with the appropriate syntax
+  (interactive
+   (list
+    current-prefix-arg
+    (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go-packages)))))
+  (save-excursion
+    (let (as line import-start)
+      (if arg
+          (setq as (read-from-minibuffer "Import as: ")))
+      (if as
+          (setq line (format "%s \"%s\"" as import))
+        (setq line (format "\"%s\"" import)))
+
+      (goto-char (point-min))
+      (if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line "$") nil t)
+          (uncomment-region (line-beginning-position) (line-end-position))
+        (cl-case (go-goto-imports)
+          (fail (message "Could not find a place to add import."))
+          (block-empty
+           (insert "\n\t" line "\n"))
+          (block
+              (save-excursion
+                (re-search-backward "^import (")
+                (setq import-start (point)))
+            (if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line "$")  import-start t)
+                (uncomment-region (line-beginning-position) (line-end-position))
+              (insert "\n\t" line)))
+          (single (insert "import " line "\n"))
+          (none (insert "\nimport (\n\t" line "\n)\n")))))))
+
+(defun go-root-and-paths ()
+  (let* ((output (process-lines go-command "env" "GOROOT" "GOPATH"))
+         (root (car output))
+         (paths (split-string (cadr output) path-separator)))
+    (cons root paths)))
+
+(defun go--string-prefix-p (s1 s2 &optional ignore-case)
+  "Return non-nil if S1 is a prefix of S2.
+If IGNORE-CASE is non-nil, the comparison is case-insensitive."
+  (eq t (compare-strings s1 nil nil
+                         s2 0 (length s1) ignore-case)))
+
+(defun go--directory-dirs (dir)
+  "Recursively return all subdirectories in DIR."
+  (if (file-directory-p dir)
+      (let ((dir (directory-file-name dir))
+            (dirs '())
+            (files (directory-files dir nil nil t)))
+        (dolist (file files)
+          (unless (member file '("." ".."))
+            (let ((file (concat dir "/" file)))
+              (if (and (file-directory-p file)
+                       (not (file-symlink-p file)))
+                  (setq dirs (append (cons file
+                                           (go--directory-dirs file))
+                                     dirs))))))
+        dirs)
+    '()))
+
+
+(defun go-packages ()
+  (funcall go-packages-function))
+
+(defun go-packages-native ()
+  "Return a list of all installed Go packages."
+  (declare (obsolete "this function does not work well with modern versions of Go. You should use `go-packages-go-list' instead." "1.7.0"))
+  (sort
+   (delete-dups
+    (cl-mapcan
+     (lambda (topdir)
+       (let ((pkgdir (concat topdir "/pkg/")))
+         (cl-mapcan (lambda (dir)
+                   (mapcar (lambda (file)
+                             (let ((sub (substring file (length pkgdir) -2)))
+                               (unless (or (go--string-prefix-p "obj/" sub) (go--string-prefix-p "tool/" sub))
+                                 (mapconcat #'identity (cdr (split-string sub "/")) "/"))))
+                           (if (file-directory-p dir)
+                               (directory-files dir t "\\.a$"))))
+                 (if (file-directory-p pkgdir)
+                     (go--directory-dirs pkgdir)))))
+     (go-root-and-paths)))
+   #'string<))
+
+(defun go-packages-go-list ()
+  "Return a list of all Go packages, using `go list'."
+  (process-lines go-command "list" "-e" "all"))
+
+(defun go-unused-imports-lines ()
+  (reverse (remove nil
+                   (mapcar
+                    (lambda (line)
+                      (when (string-match "^\\(.+\\):\\([[:digit:]]+\\):\\([[:digit:]]+\\): imported and not used: \".+\".*$" line)
+                        (let ((error-file-name (match-string 1 line))
+                              (error-line-num (match-string 2 line)))
+                          (if (string= (file-truename error-file-name) (file-truename buffer-file-name))
+                              (string-to-number error-line-num)))))
+                    (split-string (shell-command-to-string
+                                   (concat go-command
+                                           (if (string-match "_test\\.go$" buffer-file-truename)
+                                               " test -c"
+                                             (concat " build -o " null-device))
+                                           " -gcflags=-e"
+                                           " "
+                                           (shell-quote-argument (file-truename buffer-file-name)))) "\n")))))
+
+(defun go-remove-unused-imports (arg)
+  "Remove all unused imports.
+If ARG is non-nil, unused imports will be commented, otherwise
+they will be removed completely."
+  (declare (obsolete "set `gofmt-command' to goimports instead, or use LSP and gopls's \"Organize Imports\" code action." "1.7.0"))
+  (interactive "P")
+  (save-excursion
+    (let ((cur-buffer (current-buffer)) flymake-state lines)
+      (when (boundp 'flymake-mode)
+        (setq flymake-state flymake-mode)
+        (flymake-mode -1))
+      (save-some-buffers nil (lambda () (equal cur-buffer (current-buffer))))
+      (if (buffer-modified-p)
+          (message "Cannot operate on unsaved buffer")
+        (setq lines (go-unused-imports-lines))
+        (dolist (import lines)
+          (go--goto-line import)
+          (beginning-of-line)
+          (if arg
+              (comment-region (line-beginning-position) (line-end-position))
+            (go--delete-whole-line)))
+        (message "Removed %d imports" (length lines)))
+      (if flymake-state (flymake-mode 1)))))
+
+(defun godef--find-file-line-column (specifier other-window)
+  "Given a file name in the format of `filename:line:column',
+visit FILENAME and go to line LINE and column COLUMN."
+  (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))
+      ;; We've only been given a directory name
+      (funcall (if other-window #'find-file-other-window #'find-file) specifier)
+    (let ((filename (match-string 1 specifier))
+          (line (string-to-number (match-string 2 specifier)))
+          (column (string-to-number (match-string 3 specifier))))
+      (funcall (if other-window #'find-file-other-window #'find-file) filename)
+      (go--goto-line line)
+      (beginning-of-line)
+      (forward-char (1- column))
+      (if (buffer-modified-p)
+          (message "Buffer is modified, file position might not have been correct")))))
+
+(defun godef--call (point)
+  "Call godef, acquiring definition position and expression
+description at POINT."
+  (if (not (buffer-file-name (go--coverage-origin-buffer)))
+      (error "Cannot use godef on a buffer without a file name")
+    (let ((outbuf (generate-new-buffer "*godef*"))
+          (coding-system-for-read 'utf-8)
+          (coding-system-for-write 'utf-8))
+      (prog2
+          (call-process-region (point-min)
+                               (point-max)
+                               godef-command
+                               nil
+                               outbuf
+                               nil
+                               "-i"
+                               "-t"
+                               "-f"
+                               (file-truename (buffer-file-name (go--coverage-origin-buffer)))
+                               "-o"
+                               ;; Emacs point and byte positions are 1-indexed.
+                               (number-to-string (1- (position-bytes point))))
+          (with-current-buffer outbuf
+            (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n"))
+        (kill-buffer outbuf)))))
+
+(defun godef--successful-p (output)
+  (not (or (string= "-" output)
+           (string= "godef: no identifier found" output)
+           (string= "godef: no object" output)
+           (go--string-prefix-p "godef: no declaration found for " output)
+           (go--string-prefix-p "error finding import path for " output))))
+
+(defun godef--error (output)
+  (cond
+   ((godef--successful-p output)
+    nil)
+   ((string= "-" output)
+    "godef: expression is not defined anywhere")
+   (t
+    output)))
+
+(defun godef-describe (point)
+  "Describe the expression at POINT."
+  (interactive "d")
+  (condition-case nil
+      (let ((description (cdr (butlast (godef--call point) 1))))
+        (if (not description)
+            (message "No description found for expression at point")
+          (message "%s" (mapconcat #'identity description "\n"))))
+    (file-error (message "Could not run godef binary"))))
+
+(defun godef-jump (point &optional other-window)
+  "Jump to the definition of the expression at POINT."
+  (interactive "d")
+  (condition-case nil
+         (let ((file (car (godef--call point))))
+               (if (not (godef--successful-p file))
+                       (message "%s" (godef--error file))
+                 (push-mark)
+                 ;; TODO: Integrate this facility with XRef.
+                 (xref-push-marker-stack)
+                 (godef--find-file-line-column file other-window)))
+       (file-error (message "Could not run godef binary"))))
+
+(defun godef-jump-other-window (point)
+  (interactive "d")
+  (godef-jump point t))
+
+(defun go--goto-line (line)
+  (goto-char (point-min))
+  (forward-line (1- line)))
+
+(defun go--line-column-to-point (line column)
+  (save-excursion
+    (go--goto-line line)
+    (forward-char (1- column))
+    (point)))
+
+(cl-defstruct go--covered
+  start-line start-column end-line end-column covered count)
+
+(defun go--coverage-file ()
+  "Return the coverage file to use, either by reading it from the
+current coverage buffer or by prompting for it."
+  (if (boundp 'go--coverage-current-file-name)
+      go--coverage-current-file-name
+    (read-file-name "Coverage file: " nil nil t)))
+
+(defun go--coverage-origin-buffer ()
+  "Return the buffer to base the coverage on."
+  (or (buffer-base-buffer) (current-buffer)))
+
+(defun go--coverage-face (count divisor)
+  "Return the intensity face for COUNT when using DIVISOR
+to scale it to a range [0,10].
+
+DIVISOR scales the absolute cover count to values from 0 to 10.
+For DIVISOR = 0 the count will always translate to 8."
+  (let* ((norm (cond
+                ((= count 0)
+                 -0.1) ;; Uncovered code, set to -0.1 so n becomes 0.
+                ((= divisor 0)
+                 0.8) ;; covermode=set, set to 0.8 so n becomes 8.
+                (t
+                 (/ (log count) divisor))))
+         (n (1+ (floor (* norm 9))))) ;; Convert normalized count [0,1] to intensity [0,10]
+    (concat "go-coverage-" (number-to-string n))))
+
+(defun go--coverage-make-overlay (range divisor)
+  "Create a coverage overlay for a RANGE of covered/uncovered code.
+Use DIVISOR to scale absolute counts to a [0,10] scale."
+  (let* ((count (go--covered-count range))
+         (face (go--coverage-face count divisor))
+         (ov (make-overlay (go--line-column-to-point (go--covered-start-line range)
+                                                     (go--covered-start-column range))
+                           (go--line-column-to-point (go--covered-end-line range)
+                                                     (go--covered-end-column range)))))
+
+    (overlay-put ov 'face face)
+    (overlay-put ov 'help-echo (format "Count: %d" count))))
+
+(defun go--coverage-clear-overlays ()
+  "Remove existing overlays and put a single untracked overlay
+over the entire buffer."
+  (remove-overlays)
+  (overlay-put (make-overlay (point-min) (point-max))
+               'face
+               'go-coverage-untracked))
+
+(defun go--coverage-parse-file (coverage-file file-name)
+  "Parse COVERAGE-FILE and extract coverage information and
+divisor for FILE-NAME."
+  (let (ranges
+        (max-count 0))
+    (with-temp-buffer
+      (insert-file-contents coverage-file)
+      (go--goto-line 2) ;; Skip over mode
+      (while (not (eobp))
+        (let* ((parts (split-string (buffer-substring (line-beginning-position) (line-end-position)) ":"))
+               (file (car parts))
+               (rest (split-string (nth 1 parts) "[., ]")))
+
+          (cl-destructuring-bind
+              (start-line start-column end-line end-column num count)
+              (mapcar #'string-to-number rest)
+
+            (when (string= (file-name-nondirectory file) file-name)
+              (if (> count max-count)
+                  (setq max-count count))
+              (push (make-go--covered :start-line start-line
+                                      :start-column start-column
+                                      :end-line end-line
+                                      :end-column end-column
+                                      :covered (/= count 0)
+                                      :count count)
+                    ranges)))
+
+          (forward-line)))
+
+      (list ranges (if (> max-count 0) (log max-count) 0)))))
+
+(defun go-coverage (&optional coverage-file)
+  "Open a clone of the current buffer and overlay it with
+coverage information gathered via go test -coverprofile=COVERAGE-FILE.
+
+If COVERAGE-FILE is nil, it will either be inferred from the
+current buffer if it's already a coverage buffer, or be prompted
+for."
+  (interactive)
+  (let* ((cur-buffer (current-buffer))
+         (origin-buffer (go--coverage-origin-buffer))
+         (gocov-buffer-name (concat (buffer-name origin-buffer) "<gocov>"))
+         (coverage-file (or coverage-file (go--coverage-file)))
+         (ranges-and-divisor (go--coverage-parse-file
+                              coverage-file
+                              (file-name-nondirectory (buffer-file-name origin-buffer))))
+         (cov-mtime (nth 5 (file-attributes coverage-file)))
+         (cur-mtime (nth 5 (file-attributes (buffer-file-name origin-buffer)))))
+
+    (if (< (float-time cov-mtime) (float-time cur-mtime))
+        (message "Coverage file is older than the source file."))
+
+    (with-current-buffer (or (get-buffer gocov-buffer-name)
+                             (make-indirect-buffer origin-buffer gocov-buffer-name t))
+      (set (make-local-variable 'go--coverage-current-file-name) coverage-file)
+
+      (save-excursion
+        (go--coverage-clear-overlays)
+        (dolist (range (car ranges-and-divisor))
+          (go--coverage-make-overlay range (cadr ranges-and-divisor))))
+
+      (if (not (eq cur-buffer (current-buffer)))
+          (display-buffer (current-buffer) `(,go-coverage-display-buffer-func))))))
+
+(defun go-goto-function (&optional arg)
+  "Go to the function definition (named or anonymous) surrounding point.
+
+If we are on a docstring, follow the docstring down.
+If no function is found, assume that we are at the top of a file
+and search forward instead.
+
+If point is looking at the func keyword of an anonymous function,
+go to the surrounding function.
+
+If ARG is non-nil, anonymous functions are ignored."
+  (interactive "P")
+  (let ((p (point)))
+    (cond
+     ((save-excursion
+        (beginning-of-line)
+        (looking-at "^//"))
+      ;; In case we are looking at the docstring, move on forward until we are
+      ;; not anymore
+      (beginning-of-line)
+      (while (looking-at "^//")
+        (forward-line 1))
+      ;; If we are still not looking at a function, retry by calling self again.
+      (when (not (looking-at "\\<func\\>"))
+        (go-goto-function arg)))
+
+     ;; If we're already looking at an anonymous func, look for the
+     ;; surrounding function.
+     ((and (looking-at "\\<func\\>")
+           (not (looking-at "^func\\>")))
+      (re-search-backward "\\<func\\>" nil t))
+
+     ((not (looking-at "\\<func\\>"))
+      ;; If point is on the "func" keyword, step back a word and retry
+      (if (string= (symbol-name (symbol-at-point)) "func")
+          (backward-word)
+        ;; If we are not looking at the beginning of a function line, do a regexp
+        ;; search backwards
+        (re-search-backward "\\<func\\>" nil t))
+
+      ;; If nothing is found, assume that we are at the top of the file and
+      ;; should search forward instead.
+      (when (not (looking-at "\\<func\\>"))
+        (re-search-forward "\\<func\\>" nil t)
+        (go--forward-word -1))
+
+      ;; If we have landed at an anonymous function, it is possible that we
+      ;; were not inside it but below it. If we were not inside it, we should
+      ;; go to the containing function.
+      (while (and (not (go--in-function-p p))
+                  (not (looking-at "^func\\>")))
+        (go-goto-function arg)))))
+
+  (cond
+   ((go-in-comment-p)
+    ;; If we are still in a comment, redo the call so that we get out of it.
+    (go-goto-function arg))
+
+   ((and (looking-at "\\<func(") arg)
+    ;; If we are looking at an anonymous function and a prefix argument has
+    ;; been supplied, redo the call so that we skip the anonymous function.
+    (go-goto-function arg))))
+
+(defun go--goto-opening-curly-brace ()
+  ;; Find the { that starts the function, i.e., the next { that isn't
+  ;; preceded by struct or interface, or a comment or struct tag.  BUG:
+  ;; breaks if there's a comment between the struct/interface keyword and
+  ;; bracket, like this:
+  ;;
+  ;;     struct /* why? */ {
+  (go--goto-return-values)
+  (while (progn
+           (skip-chars-forward "^{")
+           (forward-char)
+           (or (go-in-string-or-comment-p)
+               (looking-back "\\(struct\\|interface\\)\\s-*{"
+                             (line-beginning-position)))))
+  (backward-char))
+
+(defun go--in-function-p (compare-point)
+  "Return t if COMPARE-POINT is inside the function immediately surrounding point."
+  (save-excursion
+    (when (not (looking-at "\\<func\\>"))
+      (go-goto-function))
+    (let ((start (point)))
+      (go--goto-opening-curly-brace)
+
+      (unless (looking-at "{")
+        (error "Expected to be looking at opening curly brace"))
+      (forward-list 1)
+      (and (>= compare-point start)
+           (<= compare-point (point))))))
+
+(defun go-goto-function-name (&optional arg)
+  "Go to the name of the current function.
+
+If the function is a test, place point after 'Test'.
+If the function is anonymous, place point on the 'func' keyword.
+
+If ARG is non-nil, anonymous functions are skipped."
+  (interactive "P")
+  (when (not (looking-at "\\<func\\>"))
+    (go-goto-function arg))
+  ;; If we are looking at func( we are on an anonymous function and
+  ;; nothing else should be done.
+  (when (not (looking-at "\\<func("))
+    (let ((words 1)
+          (chars 1))
+      (when (looking-at "\\<func (")
+        (setq words 3
+              chars 2))
+      (go--forward-word words)
+      (forward-char chars)
+      (when (looking-at "Test")
+        (forward-char 4)))))
+
+(defun go-goto-arguments (&optional arg)
+  "Go to the arguments of the current function.
+
+If ARG is non-nil, anonymous functions are skipped."
+  (interactive "P")
+  (go-goto-function-name arg)
+  (go--forward-word 1)
+  (forward-char 1))
+
+(defun go--goto-return-values (&optional arg)
+  "Go to the declaration of return values for the current function."
+  (go-goto-arguments arg)
+  (backward-char)
+  (forward-list)
+  (forward-char))
+
+(defun go-goto-return-values (&optional arg)
+  "Go to the return value declaration of the current function.
+
+If there are multiple ones contained in a parenthesis, enter the parenthesis.
+If there is none, make space for one to be added.
+
+If ARG is non-nil, anonymous functions are skipped."
+  (interactive "P")
+  (go--goto-return-values arg)
+
+  ;; Opening parenthesis, enter it
+  (when (looking-at "(")
+    (forward-char 1))
+
+  ;; No return arguments, add space for adding
+  (when (looking-at "{")
+    (insert " ")
+    (backward-char 1)))
+
+(defun go-goto-method-receiver (&optional arg)
+  "Go to the receiver of the current method.
+
+If there is none, add parenthesis to add one.
+
+Anonymous functions cannot have method receivers, so when this is called
+interactively anonymous functions will be skipped.  If called programmatically,
+an error is raised unless ARG is non-nil."
+  (interactive "P")
+
+  (when (and (not (called-interactively-p 'interactive))
+             (not arg)
+             (go--in-anonymous-funcion-p))
+    (error "Anonymous functions cannot have method receivers"))
+
+  (go-goto-function t)  ; Always skip anonymous functions
+  (forward-char 5)
+  (when (not (looking-at "("))
+    (save-excursion
+      (insert "() ")))
+  (forward-char 1))
+
+(defun go-goto-docstring (&optional arg)
+  "Go to the top of the docstring of the current function.
+
+If there is none, add one beginning with the name of the current function.
+
+Anonymous functions do not have docstrings, so when this is called
+interactively anonymous functions will be skipped.  If called programmatically,
+an error is raised unless ARG is non-nil."
+  (interactive "P")
+
+  (when (and (not (called-interactively-p 'interactive))
+             (not arg)
+             (go--in-anonymous-funcion-p))
+    (error "Anonymous functions do not have docstrings"))
+
+  (go-goto-function t)
+  (forward-line -1)
+  (beginning-of-line)
+
+  (while (looking-at "^//")
+    (forward-line -1))
+  (forward-line 1)
+  (beginning-of-line)
+
+  (cond
+   ;; If we are looking at an empty comment, add a single space in front of it.
+   ((looking-at "^//$")
+    (forward-char 2)
+    (insert (format " %s " (go--function-name t))))
+   ;; If we are not looking at the function signature, we are looking at a docstring.
+   ;; Move to the beginning of the first word of it.
+   ((not (looking-at "^func"))
+    (forward-char 3))
+   ;; If we are still at the function signature, we should add a new docstring.
+   (t
+    (forward-line -1)
+    (newline)
+    (insert "// ")
+    (insert (go--function-name t)))))
+
+(defun go--function-name (&optional arg)
+  "Return the name of the surrounding function.
+
+If ARG is non-nil, anonymous functions will be ignored and the
+name returned will be that of the top-level function.  If ARG is
+nil and the surrounding function is anonymous, nil will be
+returned."
+  (when (or (not (go--in-anonymous-funcion-p))
+            arg)
+    (save-excursion
+      (go-goto-function-name t)
+      (symbol-name (symbol-at-point)))))
+
+(defun go--in-anonymous-funcion-p ()
+  "Return t if point is inside an anonymous function, nil otherwise."
+  (save-excursion
+    (go-goto-function)
+    (looking-at "\\<func(")))
+
+(defun go-guess-gopath (&optional buffer)
+  "Determine a suitable GOPATH for BUFFER, or the current buffer if BUFFER is nil."
+  (declare (obsolete "GOPATH has been deprecated in favour of Go modules." "1.7.0"))
+  (with-current-buffer (or buffer (current-buffer))
+    (let ((gopath (cl-some (lambda (el) (funcall el))
+                           go-guess-gopath-functions)))
+      (if gopath
+          (mapconcat
+           (lambda (el) (file-truename el))
+           gopath
+           path-separator)))))
+
+(defun go-plain-gopath ()
+  "Detect a normal GOPATH, by looking for the first `src'
+directory up the directory tree."
+  (declare (obsolete "GOPATH has been deprecated in favour of Go modules." "1.7.0"))
+  (let ((d (locate-dominating-file buffer-file-name "src")))
+    (if d
+        (list d))))
+
+(defun go-set-project (&optional buffer)
+  "Set GOPATH based on `go-guess-gopath' for BUFFER.
+Set it to the current buffer if BUFFER is nil.
+
+If go-guess-gopath returns nil, that is if it couldn't determine
+a valid value for GOPATH, GOPATH will be set to the initial value
+of when Emacs was started.
+
+This function can for example be used as a
+projectile-switch-project-hook, or simply be called manually when
+switching projects."
+  (declare (obsolete "GOPATH has been deprecated in favour of Go modules." "1.7.0"))
+  (interactive)
+  (let ((gopath (or (go-guess-gopath buffer)
+                    (go-original-gopath))))
+    (setenv "GOPATH" gopath)
+    (message "Set GOPATH to %s" gopath)))
+
+(defun go-reset-gopath ()
+  "Reset GOPATH to the value it had when Emacs started."
+  (declare (obsolete "GOPATH has been deprecated in favour of Go modules." "1.7.0"))
+  (interactive)
+  (let ((gopath (go-original-gopath)))
+    (setenv "GOPATH" gopath)
+    (message "Set GOPATH to %s" gopath)))
+
+(defun go-original-gopath ()
+  "Return the original value of GOPATH from when Emacs was started."
+  (declare (obsolete "GOPATH has been deprecated in favour of Go modules." "1.7.0"))
+  (let ((process-environment initial-environment)) (getenv "GOPATH")))
+
+(defun go--insert-modified-files ()
+  "Insert the contents of each modified Go buffer into the
+current buffer in the format specified by guru's -modified flag."
+  (mapc #'(lambda (b)
+            (and (buffer-modified-p b)
+                 (buffer-file-name b)
+                 (string= (file-name-extension (buffer-file-name b)) "go")
+                 (go--insert-modified-file (buffer-file-name b) b)))
+        (buffer-list)))
+
+(defun go--insert-modified-file (name buffer)
+  (insert (format "%s\n%d\n" name (go--buffer-size-bytes buffer)))
+  (insert-buffer-substring buffer))
+
+(defun go--buffer-size-bytes (&optional buffer)
+  (message "buffer; %s" buffer)
+  "Return the number of bytes in the current buffer.
+If BUFFER, return the number of characters in that buffer instead."
+  (with-current-buffer (or buffer (current-buffer))
+    (1- (position-bytes (point-max)))))
+
+(defvar go-dot-mod-mode-map
+  (let ((map (make-sparse-keymap)))
+    map)
+  "Keymap for `go-dot-mod-mode'.")
+
+(defvar go-dot-mod-mode-syntax-table
+  (let ((st (make-syntax-table)))
+    ;; handle '//' comment syntax
+    (modify-syntax-entry ?/ ". 124b" st)
+    (modify-syntax-entry ?\n "> b" st)
+    st)
+  "Syntax table for `go-dot-mod-mode'.")
+
+(defconst go-dot-mod-mode-keywords
+  '("module" "go" "require" "replace" "exclude")
+  "All keywords for go.mod files.  Used for font locking.")
+
+(defgroup go-dot-mod nil
+  "Options specific to `go-dot-mod-mode`."
+  :group 'go)
+
+(defface go-dot-mod-module-name '((t :inherit default))
+  "Face for module name in \"require\" list."
+  :group 'go-dot-mod)
+
+(defface go-dot-mod-module-version '((t :inherit default))
+  "Face for module version in \"require\" list."
+  :group 'go-dot-mod)
+
+(defface go-dot-mod-module-semver '((t :inherit go-dot-mod-module-version))
+  "Face for module semver in \"require\" list."
+  :group 'go-dot-mod)
+
+
+(defvar go-dot-mod-font-lock-keywords
+  `(
+    (,(concat "^\\s-*\\(" (regexp-opt go-dot-mod-mode-keywords t) "\\)\\s-") 1 font-lock-keyword-face)
+    ("\\(?:^\\|=>\\)\\s-*\\([^[:space:]\n()]+\\)\\(?:\\s-+\\(v[0-9]+\\.[0-9]+\\.[0-9]+\\)\\([^[:space:]\n]*\\)\\)?" (1 'go-dot-mod-module-name) (2 'go-dot-mod-module-semver nil t) (3 'go-dot-mod-module-version nil t)))
+  "Keyword highlighting specification for `go-dot-mod-mode'.")
+
+;;;###autoload
+(define-derived-mode go-dot-mod-mode fundamental-mode "Go Mod"
+  "A major mode for editing go.mod files."
+  :syntax-table go-dot-mod-mode-syntax-table
+  (set (make-local-variable 'comment-start) "// ")
+  (set (make-local-variable 'comment-end)   "")
+  (set (make-local-variable 'comment-use-syntax) t)
+  (set (make-local-variable 'comment-start-skip) "\\(//+\\)\\s *")
+
+  (set (make-local-variable 'font-lock-defaults)
+       '(go-dot-mod-font-lock-keywords))
+  (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
+
+  ;; Go style
+  (setq indent-tabs-mode t)
+
+  ;; we borrow the go-mode-indent function so we need this buffer cache
+  (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
+  (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-dot-mod-mode))
+
+(defconst go-dot-work-mode-keywords
+  '("go" "replace" "use")
+  "All keywords for go.work files.  Used for font locking.")
+
+;;;###autoload
+(define-derived-mode go-dot-work-mode fundamental-mode "Go Work"
+  "A major mode for editor go.work files."
+  :syntax-table go-dot-mod-mode-syntax-table
+  (set (make-local-variable 'comment-start) "// ")
+  (set (make-local-variable 'comment-end)   "")
+  (set (make-local-variable 'comment-use-syntax) t)
+  (set (make-local-variable 'comment-start-skip) "\\(//+\\)\\s *")
+
+  (set (make-local-variable 'font-lock-defaults)
+       '(go-dot-work-mode-keywords))
+  (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
+
+  ;; Go style
+  (setq indent-tabs-mode t)
+
+  ;; we borrow the go-mode-indent function so we need this buffer cache
+  (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
+  (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("go\\.work\\'" . go-dot-work-mode))
+
+;; The following functions were copied (and modified) from rust-mode.el.
+;;
+;; Copyright (c) 2015 The Rust Project Developers
+;;
+;; Permission is hereby granted, free of charge, to any
+;; person obtaining a copy of this software and associated
+;; documentation files (the "Software"), to deal in the
+;; Software without restriction, including without
+;; limitation the rights to use, copy, modify, merge,
+;; publish, distribute, sublicense, and/or sell copies of
+;; the Software, and to permit persons to whom the Software
+;; is furnished to do so, subject to the following
+;; conditions:
+;;
+;; The above copyright notice and this permission notice
+;; shall be included in all copies or substantial portions
+;; of the Software.
+
+(defun go--fill-prefix-for-comment-start (line-start)
+  "Determine what to use for `fill-prefix' based on the text at LINE-START."
+  (let ((result
+         ;; Replace /* with same number of spaces
+         (replace-regexp-in-string
+          "\\(?:/\\*+?\\)[!*]?"
+          (lambda (s)
+            (let ((offset (if (eq t
+                                  (compare-strings "/*" nil nil
+                                                   s
+                                                   (- (length s) 2)
+                                                   (length s)))
+                              1 2)))
+              (make-string (1+ (- (length s) offset)) ?\x20)))
+          line-start)))
+    ;; Make sure we've got at least one space at the end
+    (if (not (= (aref result (- (length result) 1)) ?\x20))
+        (setq result (concat result " ")))
+    result))
+
+(defun go--in-comment-paragraph (body)
+  ;; We might move the point to fill the next comment, but we don't want it
+  ;; seeming to jump around on the user
+  (save-excursion
+    ;; If we're outside of a comment, with only whitespace and then a comment
+    ;; in front, jump to the comment and prepare to fill it.
+    (when (not (go-in-comment-p))
+      (beginning-of-line)
+      (when (looking-at (concat "[[:space:]\n]*" comment-start-skip))
+        (goto-char (match-end 0))))
+
+    ;; If we're at the beginning of a comment paragraph with nothing but
+    ;; whitespace til the next line, jump to the next line so that we use the
+    ;; existing prefix to figure out what the new prefix should be, rather than
+    ;; inferring it from the comment start.
+    (while (save-excursion
+             (end-of-line)
+             (and (go-in-comment-p)
+                  (save-excursion
+                    (beginning-of-line)
+                    (looking-at paragraph-start))
+                  (looking-at "[[:space:]]*$")
+                  (nth 4 (syntax-ppss (line-beginning-position 2)))))
+      (goto-char (line-beginning-position 2)))
+
+    ;; If we're on the last line of a multiline-style comment that started
+    ;; above, back up one line so we don't mistake the * of the */ that ends
+    ;; the comment for a prefix.
+    (when (save-excursion
+            (and (nth 4 (syntax-ppss (line-beginning-position 1)))
+                 (looking-at "[[:space:]]*\\*/")))
+      (goto-char (line-end-position 0)))
+    (funcall body)))
+
+(defun go--with-comment-fill-prefix (body)
+  (let*
+      ((line-string (buffer-substring-no-properties
+                     (line-beginning-position) (line-end-position)))
+       (line-comment-start
+        (when (go-in-comment-p)
+          (cond
+           ;; If we're inside the comment and see a * prefix, use it
+           ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)"
+                          line-string)
+            (match-string 1 line-string))
+           ;; If we're at the start of a comment, figure out what prefix
+           ;; to use for the subsequent lines after it
+           ((string-match (concat "[[:space:]]*" comment-start-skip) line-string)
+            (go--fill-prefix-for-comment-start
+             (match-string 0 line-string))))))
+       (fill-prefix
+        (or line-comment-start
+            fill-prefix)))
+    (funcall body)))
+
+(defun go--find-fill-prefix ()
+  (go--in-comment-paragraph
+   (lambda ()
+     (go--with-comment-fill-prefix
+      (lambda ()
+        fill-prefix)))))
+
+(defun go-fill-paragraph (&rest args)
+  "Special wrapping for `fill-paragraph'.
+This handles multi-line comments with a * prefix on each line."
+  (go--in-comment-paragraph
+   (lambda ()
+     (go--with-comment-fill-prefix
+      (lambda ()
+        (let
+            ((fill-paragraph-function
+              (if (not (eq fill-paragraph-function 'go-fill-paragraph))
+                  fill-paragraph-function))
+             (fill-paragraph-handle-comment t))
+          (apply 'fill-paragraph args)
+          t))))))
+
+(defun go--do-auto-fill (&rest args)
+  "Special wrapping for `do-auto-fill'.
+This handles multi-line comments with a * prefix on each line."
+  (go--with-comment-fill-prefix
+   (lambda ()
+     (apply 'do-auto-fill args)
+     t)))
+
+(defun go--fill-forward-paragraph (arg)
+  ;; This is to work around some funny behavior when a paragraph separator is
+  ;; at the very top of the file and there is a fill prefix.
+  (let ((fill-prefix nil)) (forward-paragraph arg)))
+
+(defun go--comment-indent-new-line (&optional arg)
+  (go--with-comment-fill-prefix
+   (lambda () (comment-indent-new-line arg))))
+
+
+
+(provide 'go-mode)
+
+;;; go-mode.el ends here
diff --git a/elpa/go-translate-20230503.303/go-translate-autoloads.el b/elpa/go-translate-20230503.303/go-translate-autoloads.el
new file mode 100644 (file)
index 0000000..358b41e
--- /dev/null
@@ -0,0 +1,99 @@
+;;; go-translate-autoloads.el --- automatically extracted autoloads  -*- lexical-binding: t -*-
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+                         (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "go-translate" "go-translate.el" (0 0 0 0))
+;;; Generated autoloads from go-translate.el
+
+(autoload 'gts-do-translate "go-translate" "\
+Do the translate" t nil)
+
+(register-definition-prefixes "go-translate" '("gts-default-translator"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-core" "gts-core.el" (0 0 0 0))
+;;; Generated autoloads from gts-core.el
+
+(register-definition-prefixes "gts-core" '("gts-"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-engine-bing" "gts-engine-bing.el" (0 0
+;;;;;;  0 0))
+;;; Generated autoloads from gts-engine-bing.el
+
+(register-definition-prefixes "gts-engine-bing" '("gts-bing-"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-engine-deepl" "gts-engine-deepl.el" (0
+;;;;;;  0 0 0))
+;;; Generated autoloads from gts-engine-deepl.el
+
+(register-definition-prefixes "gts-engine-deepl" '("gts-deepl-"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-engine-google" "gts-engine-google.el"
+;;;;;;  (0 0 0 0))
+;;; Generated autoloads from gts-engine-google.el
+
+(register-definition-prefixes "gts-engine-google" '("gts-google-"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-engine-google-rpc" "gts-engine-google-rpc.el"
+;;;;;;  (0 0 0 0))
+;;; Generated autoloads from gts-engine-google-rpc.el
+
+(register-definition-prefixes "gts-engine-google-rpc" '("gts-google-rpc-"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-engine-stardict" "gts-engine-stardict.el"
+;;;;;;  (0 0 0 0))
+;;; Generated autoloads from gts-engine-stardict.el
+
+(register-definition-prefixes "gts-engine-stardict" '("gts-stardict-"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-engine-youdao" "gts-engine-youdao.el"
+;;;;;;  (0 0 0 0))
+;;; Generated autoloads from gts-engine-youdao.el
+
+(register-definition-prefixes "gts-engine-youdao" '("gts-youdao-dict-e"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-faces" "gts-faces.el" (0 0 0 0))
+;;; Generated autoloads from gts-faces.el
+
+(register-definition-prefixes "gts-faces" '("gts-p"))
+
+;;;***
+\f
+;;;### (autoloads nil "gts-implements" "gts-implements.el" (0 0 0
+;;;;;;  0))
+;;; Generated autoloads from gts-implements.el
+
+(register-definition-prefixes "gts-implements" '("gts-"))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("go-translate-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; go-translate-autoloads.el ends here
diff --git a/elpa/go-translate-20230503.303/go-translate-pkg.el b/elpa/go-translate-20230503.303/go-translate-pkg.el
new file mode 100644 (file)
index 0000000..d7d1cec
--- /dev/null
@@ -0,0 +1,14 @@
+(define-package "go-translate" "20230503.303" "Translation framework supports multiple engines such as Google/Bing/DeepL"
+  '((emacs "27.1"))
+  :commit "59506f69503cca65291fa5ccd0a326e716338c23" :authors
+  '(("lorniu" . "lorniuNOSPAMgmail.com"))
+  :maintainers
+  '(("lorniu" . "lorniuNOSPAMgmail.com"))
+  :maintainer
+  '("lorniu" . "lorniuNOSPAMgmail.com")
+  :keywords
+  '("convenience")
+  :url "https://github.com/lorniu/go-translate")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
diff --git a/elpa/go-translate-20230503.303/go-translate.el b/elpa/go-translate-20230503.303/go-translate.el
new file mode 100644 (file)
index 0000000..100ef59
--- /dev/null
@@ -0,0 +1,90 @@
+;;; go-translate.el --- Translation framework supports multiple engines such as Google/Bing/DeepL -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+;; URL: https://github.com/lorniu/go-translate
+;; Package-Requires: ((emacs "27.1"))
+;; Keywords: convenience
+;; SPDX-License-Identifier: MIT
+;; Version: 2.0
+
+;;; Commentary:
+
+;; To be the most powerful translator on Emacs. Supports multiple translation engines such as Google, Bing, deepL.
+
+;; First, Install it via MELPA or download from github. Make sure this is on your `load-path'.
+
+;; Then, add following lines to your `.emacs':
+
+;;   (require 'go-translate)
+;;   (setq gts-translate-list '(("en" "zh")))
+;;   (setq gts-default-translator
+;;        (gts-translator
+;;         :picker (gts-prompt-picker)
+;;         :engines (list (gts-google-engine) (gts-google-rpc-engine))
+;;         :render (gts-buffer-render)))
+
+;; And start your translate with command `gts-do-translate'.
+
+;;; Code:
+
+(require 'gts-core)
+(require 'gts-implements)
+(require 'gts-engine-bing)
+(require 'gts-engine-google)
+(require 'gts-engine-google-rpc)
+(require 'gts-engine-deepl)
+(require 'gts-engine-stardict)
+(require 'gts-engine-youdao)
+
+\f
+;;; Commands
+
+(defvar gts-default-translator
+  (gts-translator :picker
+                  (gts-prompt-picker)
+                  ;;(gts-noprompt-picker)
+                  ;;(gts-noprompt-picker :texter (gts-whole-buffer-texter))
+
+                  :engines
+                  (list
+                   (gts-bing-engine)
+                   (gts-google-engine :parser (gts-google-summary-parser))
+                   (gts-google-rpc-engine)
+
+                   ;;(gts-google-engine)
+                   ;;(gts-google-rpc-engine)
+                   ;;(gts-deepl-engine :auth-key "2e20bade-88e9-02f3-169f-ab3c445d7984:fx" :pro nil)
+
+                   ;;(gts-google-engine :parser (gts-google-summary-parser))
+                   ;;(gts-google-engine :parser (gts-google-parser))
+                   ;;(gts-google-rpc-engine :parser (gts-google-rpc-summary-parser))
+                   )
+
+                  :render
+                  (gts-buffer-render)
+
+                  ;;(gts-posframe-pop-render)
+                  ;;(gts-posframe-pop-render :backcolor "#333333" :forecolor "#ffffff")
+
+                  ;;(gts-posframe-pin-render)
+                  ;;(gts-posframe-pin-render :position (cons 1200 20))
+                  ;;(gts-posframe-pin-render :width 80 :height 25 :position (cons 1000 20) :forecolor "#ffffff" :backcolor "#111111")
+
+                  ;;(gts-kill-ring-render)
+
+                  :splitter nil
+                  ;; (gts-paragraph-splitter)
+                  ))
+
+;;;###autoload
+(defun gts-do-translate ()
+  "Do the translate"
+  (interactive)
+  (gts-translate gts-default-translator))
+
+
+(provide 'go-translate)
+
+;;; go-translate.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-core.el b/elpa/go-translate-20230503.303/gts-core.el
new file mode 100644 (file)
index 0000000..998c3df
--- /dev/null
@@ -0,0 +1,673 @@
+;;; gts-core.el --- Translate engines and utils -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; The core components and utils of the translator
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'subr-x)
+(require 'eieio)
+
+(defgroup go-translate nil
+  "Translate Framework for Emacs, asynchronous and flexible."
+  :group 'external
+  :prefix 'gts-)
+
+(defvar gts-debug-p nil)
+
+\f
+;;; Utils
+
+(defun gts-aref-for (vector &rest ns)
+  "Recursively find the element in VECTOR. NS is indexes, as the path."
+  (while ns
+    (setq vector (aref vector (pop ns))))
+  vector)
+
+(defun gts-format-params (data)
+  "Format alist DATA to k=v style string."
+  (cond ((null data) nil)
+        ((stringp data) data)
+        (t (mapconcat (lambda (arg)
+                        (format "%s=%s"
+                                (url-hexify-string (format "%s" (car arg)))
+                                (url-hexify-string (format "%s" (cdr arg)))))
+                      data "&"))))
+
+(defun gts-ensure-list (object)
+  "Return OBJECT as a list."
+  (if (listp object) object (list object)))
+
+(defun gts-ensure-plain (o)
+  "Return O or the result of its execution."
+  (if (functionp o) (funcall o) o))
+
+\f
+;;; Components
+
+;; logger/http-client/cacher
+
+(defclass gts-logger () ()
+  "Used to log the messages."
+  :abstract t)
+
+(defclass gts-http-client () ()
+  "Used to send a request."
+  :abstract t)
+
+(defclass gts-cacher ()
+  ((caches  :initform nil)) ; (key . (str . timestamp))
+  "Used to cache the translate results."
+  :abstract t)
+
+;; translator/task
+
+(defclass gts-translator ()
+  ((picker     :initarg :picker   :documentation "`gts-picker' object or a function return it" :initform nil)
+   (engines    :initarg :engines  :documentation "a list of `gts-engine' objects or a function return them" :initform nil)
+   (render     :initarg :render   :documentation "`gts-render' object or a function return it" :initform nil)
+   (splitter   :initarg :splitter :documentation "`gts-splitter' object or a function return it" :initform nil)
+
+   (text       :initarg :text     :documentation "Text to be translated")
+   (sptext     :initarg :sptext   :documentation "List of strings splitted according `gts-splitter'")
+   (path       :initarg :path     :documentation "From/To language cons")
+
+   (plan-cnt   :initform 0        :documentation "count of tasks in a translation")
+   (task-queue :initform nil      :documentation "translation tasks.")
+   (state      :initform 0        :documentation "Inner state of the translator:
+0: new translator without any tasks,
+1: tasks add finished, but not running,
+2: prepare render finished, and begin to running,
+3: all tasks running finished,
+4: rendered done, all over.")
+   (version    :initform nil      :documentation "Used to distinguish between different translations")))
+
+(defclass gts-task ()
+  ((id         :initform (gensym))
+
+   (text       :initarg :text :documentation "Text that sent to engines. It maybe contains delimiters")
+   (from       :initarg :from)
+   (to         :initarg :to)
+
+   (raw        :initform nil :documentation "raw result responsed by the http-client")
+   (parsed     :initform nil :initarg :parsed :documentation "result parsed by parser, string or list")
+   (err        :initform nil :initarg :err    :documentation "error info")
+   (meta       :initform nil :initarg :meta   :documentation "extra info passed from parser to render. tbeg/tend")
+
+   (engine     :initarg :engine     :initform nil)
+   (render     :initarg :render     :initform nil)
+   (translator :initarg :translator :initform nil)))
+
+;; picker/texter
+
+(defclass gts-picker ()
+  ((single   :initarg :single :initform nil)
+   (texter   :initarg :texter))
+  "Used to pick the translation source text and from/to lang."
+  :abstract t)
+
+(defclass gts-texter () ()
+  "Used to get the initial translation text.
+Current word under cursor? Selection region? Whole line? Whole buffer? Others?
+You can implements your rules.")
+
+;; splitter
+
+(defclass gts-splitter () ()
+  "Used to split the translation text by pragraph or other rules."
+  :abstract t)
+
+;; engine/parser
+
+(defclass gts-engine ()
+  ((tag       :initarg :tag    :documentation "Used to display as name")
+   (parser    :initarg :parser :documentation "Used to parse the response result"))
+  :abstract t)
+
+(defclass gts-parser ()
+  ((tag  :initarg :tag :initform nil)))
+
+;; render
+
+(defclass gts-render () ())
+
+;; generic methods
+
+(cl-defgeneric gts-translate (translator-or-engine &rest args)
+  "Start a translation for TRANSLATOR-OR-ENGINE.
+ARGS should be text/path or task/callback.")
+
+;; silence byte-compiler
+(eieio-declare-slots task-queue text from to)
+
+\f
+;;; Logger
+
+(cl-defgeneric gts-log (logger tag message)
+  "Used to record the messages.")
+
+(defvar gts-default-logger nil "The default logger.")
+
+(defmacro gts-do-log (tag &rest messages)
+  (declare (indent 1))
+  `(when gts-debug-p
+     (if gts-default-logger
+         (cl-loop with ms = nil
+                  for message in (list ,@messages)
+                  if message do (cl-loop for m in (split-string message "\n") do (push m ms))
+                  finally (cl-loop
+                           for m in (nreverse ms)
+                           do (gts-log gts-default-logger (format "%s" ,tag) m)))
+       (error "Make sure `gts-default-logger' is available. eg:\n
+ (setq gts-default-logger (gts-buffer-logger))\n\n\n"))))
+
+\f
+;;; Cacher
+
+(defcustom gts-cache-enable t
+  "Enable the cacher if this is t."
+  :type 'boolean
+  :group 'go-translate)
+
+(defcustom gts-cache-expired-factor (* 30 60)
+  "Cache alive time based on this.
+Make word live longer time than sentence."
+  :type 'integer
+  :group 'go-translate)
+
+(defvar gts-default-cacher nil "The default cacher.")
+
+(cl-defgeneric gts-cache-get (cacher task)
+  "Get item for TASK from CACHER.")
+
+(cl-defgeneric gts-cache-set (cacher task result)
+  "Add RESULT fro TASK to CACHER.")
+
+(cl-defmethod gts-cache-key ((_ gts-cacher) task)
+  (with-slots (text from to engine) task
+    (sha1 (format "%s-%s-%s-%s"
+                  text from to
+                  (eieio-object-class-name engine)))))
+
+(cl-defmethod gts-clear-expired ((cacher gts-cacher))
+  (with-slots (caches expired) cacher
+    (cl-delete-if (lambda (c) (> (time-to-seconds) (cddr c))) caches)))
+
+(cl-defmethod gts-clear-all ((cacher gts-cacher))
+  (oset cacher caches nil))
+
+\f
+;;; Http Client
+
+(cl-defgeneric gts-request (http-client url &key done fail data headers)
+  "Use HTTP-CLIENT to request a URL with DATA.
+When success execute CALLBACK, or execute ERRORBACK."
+  (:method (&rest _) (user-error "Method `gts-request' is not implement")))
+
+(defvar gts-default-http-client nil
+  "The default http client used to send a request.")
+
+(cl-defun gts-do-request (url &key done fail data headers)
+  "Helper for `gts-request'.
+DONE is a callback based on the responsed result buffer.
+FAIL is the callback for any error occured in request or parse."
+  (if (and gts-default-http-client
+           (eieio-object-p gts-default-http-client)
+           (object-of-class-p gts-default-http-client 'gts-http-client))
+      (let ((tag (format "%s" (eieio-object-class-name gts-default-http-client)))
+            (data (gts-format-params data)))
+        (gts-do-log tag
+          (format "Start! (%s)" url)
+          (if headers (format "    HEADER: %s" headers))
+          (if data (format "    DATA:   %s" data)))
+        (gts-request gts-default-http-client url
+                     :headers headers
+                     :data data
+                     :done (lambda ()
+                             (gts-do-log tag (format "DONE! (%s)" url))
+                             (condition-case err
+                                 (funcall done)
+                               (error
+                                (gts-do-log tag (format "FAIL in callback! (%s) %s" url err))
+                                (funcall fail err))))
+                     :fail (lambda (status)
+                             (gts-do-log tag (format "FAIL! (%s) %s" url status))
+                             (funcall fail status))))
+    (funcall fail "Make sure `gts-default-http-client' is available. eg:\n
+ (setq gts-default-http-client (gts-url-http-client))\n\n\n")))
+
+\f
+;;; Picker/Texter
+
+(cl-defgeneric gts-pick (picker)
+  "Get the source text and translate from/to path from PICKER.")
+
+(cl-defgeneric gts-path (picker text)
+  "Use to get the translation path for TEXT from PICKER.")
+
+(cl-defgeneric gts-next-path (picker text path &optional backwardp)
+  "Get the next available path.")
+
+(cl-defgeneric gts-text (texter)
+  "Get the init translation source text via TEXTER.")
+
+(defvar gts-picker-last-path nil)
+
+(defcustom gts-translate-list nil
+  "Setup your translate languages. Just like:
+
+Single:
+
+ (setq gts-translate-list \\='((\"en\" \"zh\")))
+
+Or multiple:
+
+ (setq gts-translate-list \\='((\"en\" \"zh\") (\"en\" \"fr\")))
+
+The picker will give the translate from/to pair according this."
+  :type 'alist
+  :group 'go-translate)
+
+(defcustom gts-picker-lang-match-alist
+  (list
+   (cons "zh"  "\\cc")
+   (cons "ja"  (lambda (text) (string-match-p "\\cj" text)))
+   (cons "ru" "\\cy"))
+  "Setup language match rules.
+Used to pick the right language for current translate text.
+Key should be the language, value should be a regexp string or function.
+Picker choose path according this first. If non-match, choose other paths."
+  :type 'alist
+  :group 'go-translate)
+
+(defun gts-picker-filter-paths (paths text)
+  "Filter PATHS according TEXT.
+The rules are provided by `gts-picker-lang-match-alist'."
+  (when text
+    (cl-loop with text = (with-temp-buffer ; clear punctuations
+                           (insert text)
+                           (goto-char (point-min))
+                           (while (re-search-forward "\\s.\\|\n" nil t) (replace-match ""))
+                           (buffer-string))
+             for path in paths
+             for matcher = (let ((p (cdr (cl-find-if ; get match rule
+                                          (lambda (item) (string-match-p (format "^%s$" (car item)) (car path)))
+                                          gts-picker-lang-match-alist))))
+                             (if (stringp p) (lambda (text) (string-match-p p text)) p))
+             when (and matcher (funcall matcher text))
+             collect path)))
+
+(cl-defmethod gts-paths ((picker gts-picker) &optional keep-order)
+  "All paths can be picked by PICKER.
+When KEEP-ORDER is not nil then do not try to push last path to the first place."
+  (when (or (null gts-translate-list) (not (consp (car gts-translate-list))))
+    (user-error "Please make sure you set the avaiable `gts-translate-list'. eg:\n
+ (setq gts-translate-list '((\"en\" \"zh\") (\"en\" \"ja\"))\n\n"))
+  (let (paths)
+    (cl-loop for (lang1 . lang2) in gts-translate-list
+             do (let ((lang2 (if (consp lang2) (car lang2) lang2)))
+                  (cl-pushnew (cons lang1 lang2) paths :test #'equal)
+                  (unless (oref picker single) (cl-pushnew (cons lang2 lang1) paths :test #'equal))))
+    (setq paths (nreverse paths))
+    (if (and (not keep-order)
+             gts-picker-last-path (member gts-picker-last-path paths))
+        (cons gts-picker-last-path (remove gts-picker-last-path paths))
+      paths)))
+
+(cl-defmethod gts-path ((picker gts-picker) text)
+  "Default path for TEXT used by PICKER.
+Return the first path matching TEXT. If no path matches, return the non-nil
+`gts-picker-last-path', then the first available path."
+  (if-let* ((paths (gts-paths picker))
+            (matched (gts-picker-filter-paths paths text)))
+      (car matched)
+    (or (let ((langs (mapcar #'car gts-picker-lang-match-alist)))
+          (cl-find-if (lambda (p) (not (member (car p) langs))) paths))
+        (car paths))))
+
+(cl-defmethod gts-next-path ((picker gts-picker) text path &optional backwardp)
+  (let (candidates idx (paths (gts-paths picker 'keep-order)))
+    (cl-loop for path in (append (gts-picker-filter-paths paths text) paths)
+             do (cl-pushnew path candidates :test 'equal))
+    (setq candidates (reverse candidates))
+    (setq idx (cl-position path candidates :test 'equal))
+    (if backwardp
+        (elt candidates (if (= 0 idx) (- (length candidates) 1) (- idx 1)))
+      (elt candidates (if (= (+ 1 idx) (length candidates)) 0 (+ 1 idx))))))
+
+\f
+;;; Splitter
+
+(cl-defgeneric gts-split (gts-splitter text)
+  "Split TEXT to several parts.
+Return list of string, or nil if split failed.")
+
+\f
+;;; Engine/Parser
+
+(cl-defmethod gts-translate ((engine gts-engine) _task _callback)
+  "Translate and update TASK with parsed result using ENGINE.
+The CALLBACK should contain the render logic."
+  (user-error "Method `gts-translate' of %s is not implement" (eieio-object-class-name engine)))
+
+(cl-defmethod gts-translate :around ((engine gts-engine) task callback)
+  "Add cache and log support."
+  (with-slots (id render translator) task
+    (if-let ((render-name (eieio-object-class-name render))
+             (engine-name (eieio-object-class-name engine))
+             (cache (gts-cache-get gts-default-cacher task)))
+        ;; try cache
+        (let ((engines (gts-get translator 'engines)))
+          (gts-update-raw task cache)
+          (gts-parse (oref engine parser) task)
+          (if (cdr engines) (gts-me-out render task)
+            (gts-out render task))
+          (gts-do-log 'render (format "%s: done with cache!" id)))
+      ;; request from engine
+      (gts-do-log 'engine (format "%s: starting %s..." id engine-name))
+      (cl-call-next-method engine task
+                           (lambda ()
+                             (gts-do-log 'engine (format "%s: %s done!" id engine-name))
+                             (funcall callback)
+                             ;; refresh cache
+                             (with-slots (err raw parsed) task
+                               (unless err
+                                 (gts-cache-set gts-default-cacher task raw))))))))
+
+(cl-defgeneric gts-with-token (engine done fail)
+  (:documentation "Get token for ENGINE, if success then DONE is called.")
+  (declare (indent 1)))
+
+(cl-defgeneric gts-tts (engine text lang)
+  "TTS, speak TEXT with LANG."
+  (:method ((e gts-engine) &rest _)
+           (user-error "No TTS service found on engine `%s'" (oref e tag))))
+
+(cl-defgeneric gts-parse (parser task)
+  (:documentation "Parse the raw result and update the parsed result into TASK.")
+  (:method ((_o gts-parser) task)
+           ;; do nothing
+           (gts-update-parsed task (oref task raw))))
+
+\f
+;;; Render
+
+(cl-defgeneric gts-pre (render translator)
+  (:documentation "Pre-render before request success.
+Do some preparation for the gts-out.")
+  (:method :after ((render gts-render) _)
+           (gts-do-log 'render (format "prepare render with %s finished" (eieio-object-class-name render))))
+  (:method ((_o gts-render) (_t gts-translator)) ()))
+
+(cl-defgeneric gts-out (render task)
+  (:documentation "Render the TASK, default output to *Messages*.")
+  (:method :after ((render gts-render) task)
+           (gts-do-log 'render (format "%s: %s finished" (oref task id) (eieio-object-class-name render))))
+  (:method ((_ gts-render) task)
+           (with-slots (err parsed) task
+             (when (and (not err) (listp parsed))
+               (setq parsed (string-join parsed "\n\f\n")))
+             (setq parsed (string-trim (format "%s" (or err parsed))))
+             (setq parsed (propertize parsed 'face 'font-lock-keyword-face))
+             (message "-+-G-+-T-+-S-+-")
+             (message parsed ))))
+
+(cl-defgeneric gts-me-pre (render translator)
+  (:documentation "Pre-render for TRANSLATOR.
+It used only when multiple engines exists.
+Default dispatch to gts-pre when first pre-render.")
+  (:method :after ((render gts-render) _)
+           (gts-do-log 'render (format "me prepare render with %s finished" (eieio-object-class-name render))))
+  (:method ((render gts-render) translator)
+           (with-slots (task-queue state) translator
+             (when (= state 1)
+               (gts-pre render translator)
+               (setf state 2)))))
+
+(cl-defgeneric gts-me-out (render task)
+  (:documentation "Render TASK with RENDER.
+It used only when multiple engines exists.
+Default dispatch to gts-out with all results concated.")
+  (:method :after ((render gts-render) task)
+           (gts-do-log 'render (format "%s: %s finished" (oref task id) (eieio-object-class-name render))))
+  (:method ((render gts-render) task)
+           (let ((translator (oref task translator)) results)
+             ;; show only when all tasks finished.
+             (when (= (oref translator state) 3)
+               (dolist (task (oref translator task-queue))
+                 (with-slots (err parsed engine) task
+                   (when (and (not err) (listp parsed))
+                     (setq parsed (string-join parsed "\f")))
+                   (push (cons (format "[%s]" (oref engine tag)) (or err parsed)) results)))
+               (setq results (nreverse results))
+               (let* ((newlinep (cl-find-if (lambda (r) (string-match-p "\n" (cdr r))) results))
+                      (concated (mapconcat (lambda (r)
+                                             (if newlinep
+                                                 (concat (propertize (concat (car r) "\n") 'face 'gts-pop-posframe-me-header-2-face)
+                                                         (format "\n%s" (cdr r)))
+                                               (concat (propertize (format "%-7s" (car r)) 'face 'gts-pop-posframe-me-header-face)
+                                                       (format " %s" (cdr r)))))
+                                           results
+                                           (if newlinep "\n\n" "\n"))))
+                 (oset translator state 4)
+                 (gts-out render (gts-task :parsed concated :translator translator)))))))
+
+\f
+;;; TTS
+
+(defcustom gts-tts-speaker (or (executable-find "mpv")
+                               (executable-find "mplayer"))
+  "The program to use to speak the translation.
+
+On Windows, if it is not found, will fallback to use `powershell`
+to do the job. Although it is not perfect, it seems to work."
+  :type 'string
+  :group 'go-translate)
+
+(defcustom gts-tts-try-speak-locally t
+  "If t then will fallback to locally TTS service when engine's TTS failed.
+For example, it will use powershell on Windows to speak the word
+when this is set to t."
+  :type 'boolean
+  :group 'go-translate)
+
+(defvar gts-tts-playing-process nil)
+
+(defun gts-tts-speak-buffer-data ()
+  "Speak the current buffer's data."
+  (gts-tts-try-interrupt-playing-process)
+  (let ((proc (make-process :name (format "gts-tts-process-%s" (+ 1000 (random 1000)))
+                            :command (append (split-string gts-tts-speaker) (list "-"))
+                            :buffer nil
+                            :noquery t
+                            :connection-type 'pipe)))
+    (setq gts-tts-playing-process proc)
+    (process-send-region proc (point-min) (point-max))
+    (if (process-live-p proc) (process-send-eof proc))))
+
+(defun gts-tts-try-interrupt-playing-process ()
+  (when (and gts-tts-playing-process (process-live-p gts-tts-playing-process))
+    (ignore-errors (kill-process gts-tts-playing-process))
+    (setq gts-tts-playing-process nil)))
+
+(defun gts-do-tts (text lang &optional engine)
+  "Speak TEXT in LANG with possible tts service by ENGINE.
+If engine failed, then try locally TTS if `gts-tts-try-speak-locally' is set."
+  (condition-case err
+      (if engine
+          (if gts-tts-speaker
+              (gts-tts engine text lang)
+            (user-error "No mpv/mplayer found"))
+        (user-error "No engine TTS service provided"))
+    (error (if gts-tts-try-speak-locally
+               (cond ((executable-find "powershell")
+                      (let ((cmd (format "$w = New-Object -ComObject SAPI.SpVoice; $w.speak(\\\"%s\\\")" text)))
+                        (shell-command (format "powershell -Command \"& {%s}\""
+                                               (encode-coding-string
+                                                (replace-regexp-in-string "\n" " " cmd)
+                                                (keyboard-coding-system))))))
+                     (t (message "[TTS] %s" (cadr err))))))))
+
+\f
+;;; Translator
+
+(defvar gts-text-delimiter "34587612321123")
+
+(defvar gts-current-command nil "The command invoked by `gts-translate'.")
+(defvar gts-current-picker nil "Picker used in current translate.")
+(defvar gts-current-engines nil "Engines used in current translate.")
+(defvar gts-current-render nil "Render used in current translate.")
+
+(cl-defmethod initialize-instance :after ((this gts-translator) &rest _)
+  (unless (gts-ensure-plain (oref this render))
+    (oset this render (gts-render)))
+  (unless (gts-ensure-plain (oref this picker))
+    (oset this picker (gts-noprompt-picker))))
+
+(cl-defmethod gts-get ((this gts-translator) &rest slots)
+  "Return the normalized value of SLOTS in THIS translator."
+  (cl-loop for slot in slots
+           for value =
+           (cl-flet ((ensure (v) (or v (user-error "No %s found in current translator" slot))))
+             (pcase slot
+               ('splitter   (gts-ensure-plain (slot-value this slot)))
+               ('picker     (ensure (gts-ensure-plain (or gts-current-picker (slot-value this slot)))))
+               ('engines    (ensure (gts-ensure-list (gts-ensure-plain (or gts-current-engines (slot-value this slot))))))
+               ('render     (ensure (gts-ensure-plain (or gts-current-render (slot-value this slot)))))
+               (_           (slot-value this slot))))
+           collect value into values
+           finally (return (if (cadr slots) (apply #'cl-values values) (car values)))))
+
+(cl-defmethod gts-init ((this gts-translator) text path)
+  "Init all status and tasks in THIS translator."
+  ;; input
+  (unless (and text path)
+    (let ((input (gts-pick (gts-get this 'picker))))
+      (setq text (cl-nth-value 0 input))
+      (setq path (cl-nth-value 1 input))))
+  (oset this text text)
+  (oset this path path)
+  (setq gts-picker-last-path path)
+  (gts-do-log 'translator (format "\nBegin to translate %s" path))
+  (gts-do-log 'text text)
+  (cl-multiple-value-bind (splitter engines render) (gts-get this 'splitter 'engines 'render 'version)
+    ;; reset
+    (oset this state 0)
+    (oset this task-queue nil)
+    (oset this plan-cnt (length engines))
+    (oset this version (time-to-seconds))
+    ;; tasks
+    (let ((splitted (if splitter (gts-split splitter text))))
+      (oset this sptext splitted)
+      (cl-loop for engine in engines
+               for task = (gts-task :translator this
+                                    :engine engine
+                                    :render render
+                                    :from (car path) :to (cdr path)
+                                    :text (if splitted (string-join splitted (concat "\n" gts-text-delimiter "\n")) text))
+               do (gts-add-task this task))
+      (gts-do-log 'text (format "splitted? %s!" (if splitted "Yes" "No"))))))
+
+(cl-defmethod gts-add-task ((this gts-translator) task)
+  "Add a new TASK to THIS translator."
+  (with-slots (id engine render) task
+    (with-slots (text path plan-cnt task-queue state) this
+      (when (= state 0)
+        (setf task-queue (append task-queue (list task)))
+        (gts-do-log 'translator
+          (format "- add task %s: (%s/%s)" id
+                  (eieio-object-class-name engine) (eieio-object-class-name render))))
+      (when (and (= state 0) (= (length task-queue) plan-cnt))
+        (setf state 1)))))
+
+(cl-defmethod gts-done-tasks ((this gts-translator))
+  "Return the finished tasks in THIS translator."
+  (with-slots (task-queue) this
+    (cl-remove-if-not
+     (lambda (task) (or (oref task err) (oref task raw))) task-queue)))
+
+(defun gts-update-raw (task resp &optional error)
+  "Update non-parsed RESP result to TASK.
+If ERROR is not nil, then update task with failure."
+  (declare (indent 1))
+  (with-slots (id raw err translator) task
+    (with-slots (task-queue plan-cnt state) translator
+      (setf raw resp)
+      (setf err error)
+      (when (and (= state 2) (= plan-cnt (length (gts-done-tasks translator))))
+        (setf state 3))
+      (gts-do-log 'translator (format "%s: **responsed** %s" id (or error "success!"))))))
+
+(defun gts-update-parsed (task result &optional metadata filter)
+  "Update TASK with parsed RESULT, update METADATA if exists.
+FILTER the parsed result if necessary, it's a function take parsed result
+as argument."
+  (declare (indent 1))
+  (with-slots (id raw parsed err meta translator) task
+    (if raw
+        (condition-case er
+            (let ((r (if (or (listp result) (not (gts-get translator 'sptext)))
+                         result
+                       (gts-do-log 'translator
+                         (format "%s: result contains delimiter, so split to list" id))
+                       (split-string result gts-text-delimiter nil "[ \t\n\r]+"))))
+              (setf parsed (if filter (funcall filter r) r))
+              (if metadata (setf meta metadata)))
+          (error (setf err er)))
+      (gts-update-raw task t "Mybe Null Response"))))
+
+(defun gts-render-fail (task error)
+  "Directly render the ERROR message and finish the TASK."
+  (declare (indent 1))
+  (gts-do-log 'translator (format "error? %s" error))
+  (if task
+      (with-slots (translator render) task
+        (let ((engines (gts-get translator 'engines)))
+          (gts-update-raw task nil error)
+          (if (cdr engines)
+              (gts-me-out render task)
+            (gts-out render task))))
+    (signal 'error error)))
+
+(cl-defmethod gts-translate ((this gts-translator) &optional text path)
+  "Fire a translation for THIS translator instance.
+When TEXT and PATH is nil then pick them via `gts-pick'."
+  ;; dynamic
+  (setq gts-current-command this-command)
+  (setq gts-current-picker (oref this picker))
+  (setq gts-current-engines (oref this engines))
+  (setq gts-current-render (oref this render))
+  ;; initialize
+  (gts-init this text path)
+  (cl-multiple-value-bind (engines render tasks text path)
+      (gts-get this 'engines 'render 'task-queue 'text 'path)
+    (let ((buf (current-buffer)))
+      ;; single engine
+      (if (not (cdr engines))
+          (let ((task (car tasks)))
+            (gts-pre render this)
+            (gts-translate (car engines) task
+                           (lambda ()
+                             (with-current-buffer buf
+                               (gts-out render task)))))
+        ;; multiple engines
+        (gts-me-pre render this)
+        (dolist (task tasks)
+          (condition-case er
+              (gts-translate (oref task engine) task
+                             (lambda ()
+                               (with-current-buffer buf
+                                 (gts-me-out render task))))
+            (error
+             (gts-render-fail task er))))
+        (gts-me-out render (gts-task :text text :from (car path) :to (cdr path) :translator this))))))
+
+
+(provide 'gts-core)
+
+;;; gts-core.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-bing.el b/elpa/go-translate-20230503.303/gts-engine-bing.el
new file mode 100644 (file)
index 0000000..202d34b
--- /dev/null
@@ -0,0 +1,155 @@
+;;; gts-engine-bing.el --- Microsoft Translate -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; https://bing.com/translator
+
+;;; Code:
+
+(require 'gts-implements)
+
+(defclass gts-bing-parser (gts-parser) ())
+
+(defclass gts-bing-engine (gts-engine)
+  ((tag       :initform "Bing")
+   (base-url  :initform "https://www.bing.com")
+   (sub-url   :initform "/ttranslatev3")
+
+   (tld-url   :initform nil)
+   (ig        :initform nil)
+   (key       :initform nil)
+   (token     :initform nil)
+   (last-time :initform nil)
+   (expired-time :initform (* 30 60))
+
+   (ttsk-url  :initform "/tfetspktok")
+   (tts-url   :initform "https://%s.tts.speech.microsoft.com/cognitiveservices/v1")
+   (tts-tpl   :initform "<speak version='1.0' xml:lang='%s'><voice xml:lang='%s' xml:gender='Female' name='%s'><prosody rate='-20.00%%'>%s</prosody></voice></speak>")
+
+   (parser    :initform (gts-bing-parser))))
+
+\f
+;;; Engine
+
+(defvar gts-bing-extra-langs-mapping '(("zh" . "zh-Hans")))
+
+(defvar gts-bing-token-maybe-invalid nil)
+
+(cl-defmethod gts-get-lang ((_ gts-bing-engine) lang)
+  (or (cdr-safe (assoc lang gts-bing-extra-langs-mapping)) lang))
+
+(cl-defmethod gts-token-available-p ((engine gts-bing-engine))
+  (with-slots (token key ig last-time expired-time) engine
+    (and token key ig last-time
+         (not gts-bing-token-maybe-invalid)
+         (< (- (time-to-seconds) last-time) expired-time))))
+
+(cl-defmethod gts-with-token ((engine gts-bing-engine) done fail)
+  (with-slots (token key ig base-url) engine
+    (if (gts-token-available-p engine)
+        (funcall done)
+      (gts-do-request (concat base-url "/translator")
+                      :done
+                      (lambda ()
+                        (let (key token ig tld)
+                          (re-search-forward "curUrl=.*/\\([a-z]+\\.bing.com\\)")
+                          (setq tld (match-string 1))
+                          (re-search-forward "\"ig\":\"\\([^\"]+\\).*params_AbusePreventionHelper = \\[\\([0-9]+\\),\"\\([^\"]+\\)")
+                          (setq ig (match-string 1) key (match-string 2) token (match-string 3))
+                          (oset engine ig ig)
+                          (oset engine key key)
+                          (oset engine token token)
+                          (oset engine last-time (time-to-seconds))
+                          (oset engine tld-url (concat "https://" tld))
+                          (setq gts-bing-token-maybe-invalid nil)
+                          (gts-do-log 'bing (format "url: %s\nkey: %s\ntoken: %s\nig: %s" tld key token ig))
+                          (funcall done)))
+                      :fail fail))))
+
+(cl-defmethod gts-translate ((engine gts-bing-engine) task rendercb)
+  (gts-with-token engine
+    (lambda ()
+      (with-slots (text from to raw) task
+        (with-slots (tld-url sub-url token key ig parser) engine
+          (gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.1" tld-url sub-url ig)
+                          :headers `(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8"))
+                          :data `(("fromLang" . ,(gts-get-lang engine from))
+                                  ("to"       . ,(gts-get-lang engine to))
+                                  ("text"     . ,text)
+                                  ("key"      . ,key)
+                                  ("token"    . ,token))
+                          :done (lambda ()
+                                  (gts-update-raw task (buffer-string))
+                                  (gts-parse parser task)
+                                  (funcall rendercb))
+                          :fail (lambda (err)
+                                  (gts-render-fail task
+                                    (cond ((ignore-errors (= (caddar err) 429))
+                                           "[429] Too many requests! Please retry later")
+                                          (t err))))))))
+    (lambda (err)
+      (gts-render-fail task
+        (format "Error occurred when request for token.\n\n%s" err)))))
+
+\f
+;;; TTS
+
+(defvar gts-bing-tts-langs-mapping '(("zh" . ("zh-CN" . "zh-CN-XiaoxiaoNeural"))
+                                     ("en" . ("en-US" . "en-US-AriaNeural"))
+                                     ("fr" . ("fr-CA" . "fr-CA-SylvieNeural"))
+                                     ("de" . ("de-DE" . "de-DE-KatjaNeural"))))
+
+(cl-defmethod gts-tts-payload ((engine gts-bing-engine) lang text)
+  (with-slots (tts-tpl) engine
+    (let (l n (mt (assoc lang gts-bing-tts-langs-mapping)))
+      (if mt (setq l (cadr mt) n (cddr mt))
+        (user-error "Add the mapping of your language into `gts-bing-tts-langs-mapping' :)"))
+      (format tts-tpl l l n (encode-coding-string text 'utf-8)))))
+
+(cl-defmethod gts-tts ((engine gts-bing-engine) text lang)
+  (gts-with-token engine
+    (lambda ()
+      (with-slots (tld-url sub-url token key ig parser ttsk-url tts-url tts-tpl) engine
+        (gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.2" tld-url ttsk-url ig)
+                        :headers '(("content-type" . "application/x-www-form-urlencoded"))
+                        :data `(("token" . ,token) ("key" . ,key))
+                        :done (lambda ()
+                                (let* ((json (json-read))
+                                       (token (cdr (assoc 'token json)))
+                                       (region (cdr (assoc 'region json))))
+                                  (gts-do-log 'bing-tts (format "token: %s\nregion: %s" token region))
+                                  (gts-do-request (format tts-url region)
+                                                  :data (gts-tts-payload engine lang text)
+                                                  :headers `(("content-type" . "application/ssml+xml")
+                                                             ("authorization" . ,(format "Bearer %s" token))
+                                                             ("x-microsoft-outputformat" . "audio-16khz-32kbitrate-mono-mp3"))
+                                                  :done (lambda ()
+                                                          (gts-tts-speak-buffer-data))
+                                                  :fail (lambda (_)
+                                                          (user-error "[BING-TTS] error when play sound")))))
+                        :fail (lambda (err) (user-error "%s" err)))))
+    (lambda (_) (user-error "Failed to get token"))))
+
+\f
+;;; Parser
+
+(cl-defmethod gts-parse ((_ gts-bing-parser) task)
+  (with-temp-buffer
+    (set-buffer-multibyte t)
+    (insert (oref task raw))
+    (decode-coding-region (point-min) (point-max) 'utf-8)
+    (gts-do-log 'bing (string-trim (buffer-string)))
+    (goto-char (point-min))
+    (if-let* ((json (json-read))
+              (result (ignore-errors (cdr (assoc 'text (aref (cdr (assoc 'translations (aref json 0))) 0))))))
+        (gts-update-parsed task result)
+      (setq gts-bing-token-maybe-invalid t) ; refresh token when error occurred
+      (gts-update-raw task nil (buffer-string)))))
+
+
+(provide 'gts-engine-bing)
+
+;;; gts-engine-bing.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-deepl.el b/elpa/go-translate-20230503.303/gts-engine-deepl.el
new file mode 100644 (file)
index 0000000..9fa0a39
--- /dev/null
@@ -0,0 +1,127 @@
+;;; gts-engine-deepl.el --- Translate Engine for DeepL -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; https://www.deepl.com
+
+;;; Code:
+
+(require 'gts-implements)
+
+\f
+;;; Components
+
+(defclass gts-deepl-parser (gts-parser) ())
+
+(defclass gts-deepl-engine (gts-engine)
+  ((tag      :initform "DeepL")
+   (pro      :initform nil :initarg :pro :documentation "If your key is PRO version")
+   (free-url :initform "https://api-free.deepl.com")
+   (pro-url  :initform "https://api.deepl.com")
+   (sub-url  :initform "/v2/translate")
+   (auth-key :initform nil :initarg :auth-key)
+   (parser   :initform (gts-deepl-parser))))
+
+(cl-defmethod initialize-instance :after ((engine gts-deepl-engine) &rest _)
+  (unless (oref engine auth-key)
+    (user-error "You should provide a auth-key when instance gts-deepl-engine")))
+
+\f
+;;; Utils
+
+(defcustom gts-deepl-fill-enable t
+  "Controller wheather try to improve the input and output.
+Default behavior is removing excess linebreaks in input for better
+translation effect, and filling the output for better reading experience.
+You can override the behaviors by :around the method `gts-deepl-fill-input'
+or `gts-deepl-fill-output'."
+  :type 'boolean
+  :group 'go-translate)
+
+(cl-defmethod gts-deepl-fill-input (text)
+  "Improve the input TEXT for better translation effect.
+Mainly remove excess linebreaks. I want to skip unfill on comments and codes,
+but don't know how to implement easily. To make it better later, maybe."
+  (if gts-deepl-fill-enable
+      (with-temp-buffer
+        (insert text)
+        (let ((fill-column (* 2 (point-max))))
+          (fill-region (point-min) (point-max)))
+        (buffer-string))
+    text))
+
+(cl-defmethod gts-deepl-fill-output (text)
+  "Improve the output TEXT for better reading experience.
+Mainly fill the text to suitable length."
+  (if gts-deepl-fill-enable
+      (with-temp-buffer
+        (insert text)
+        (fill-region (point-min) (point-max))
+        (buffer-string))
+    text))
+
+\f
+;;; Engine
+
+(defvar gts-deepl-langs-mapping '(("en" . "EN")
+                                  ("zh" . "ZH")
+                                  ("de" . "DE") ; German
+                                  ("fr" . "FR") ; French
+                                  ("it" . "IT") ; Italian
+                                  ("ja" . "JA") ; Japanese
+                                  ("es" . "ES") ; Spanish
+                                  ("nl" . "NL") ; Dutch
+                                  ("pl" . "PL") ; Polish
+                                  ("pt" . "PT") ; Portuguese (all Portuguese varieties mixed)
+                                  ("ru" . "RU") ; Russian
+                                  ))
+
+(cl-defmethod gts-get-lang ((_ gts-deepl-engine) lang)
+  (let ((mapping (assoc lang gts-deepl-langs-mapping)))
+    (if mapping (cdr mapping)
+      (user-error
+       "Language %s is not supported by DeepL.\nSupported list: %s"
+       lang (mapconcat #'car gts-deepl-langs-mapping ", ")))))
+
+(cl-defmethod gts-gen-url ((engine gts-deepl-engine))
+  (with-slots (free-url pro-url sub-url pro) engine
+    (format "%s%s" (if pro pro-url free-url) sub-url)))
+
+(cl-defmethod gts-translate ((engine gts-deepl-engine) task rendercb)
+  (with-slots (text from to) task
+    (with-slots (auth-key parser) engine
+      (gts-do-request (gts-gen-url engine)
+                      :headers `(("Content-Type"   . "application/x-www-form-urlencoded;charset=UTF-8")
+                                 ("Authorization"  . ,(concat "DeepL-Auth-Key " auth-key)))
+                      :data    `(("text"           . ,(gts-deepl-fill-input text))
+                                 ("source_lang"    . ,(gts-get-lang engine from))
+                                 ("target_lang"    . ,(gts-get-lang engine to)))
+                      :done (lambda ()
+                              (gts-update-raw task (buffer-string))
+                              (gts-parse parser task)
+                              (funcall rendercb))
+                      :fail (lambda (err)
+                              (gts-render-fail task
+                                (if (ignore-errors (= (caddar err) 403))
+                                    "[403] http error, make sure your auth_key is correct"
+                                  err)))))))
+
+\f
+;;; Parser
+
+(cl-defmethod gts-parse ((_ gts-deepl-parser) task)
+  (let* ((json (json-read-from-string (oref task raw)))
+         (str (mapconcat (lambda (r) (cdr (cadr r))) (cdar json) "\n"))
+         (filter (lambda (parsed)
+                   (cl-loop for p in (gts-ensure-list parsed)
+                            collect (gts-deepl-fill-output p) into ps
+                            finally (return (if (cdr ps) ps (car ps)))))))
+    (gts-update-parsed task (decode-coding-string str 'utf-8) nil filter)))
+
+
+(provide 'gts-engine-deepl)
+
+;;; gts-engine-deepl.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-google-rpc.el b/elpa/go-translate-20230503.303/gts-engine-google-rpc.el
new file mode 100644 (file)
index 0000000..8c784c4
--- /dev/null
@@ -0,0 +1,207 @@
+;;; gts-engine-google-rpc.el --- Google translation with rpc api -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; http://translate.google.com, new RPC API
+
+;;; Code:
+
+(require 'gts-engine-google)
+
+(defcustom gts-google-rpc-base-url "https://translate.google.com"
+  "The base url of Google translate used by google-rpc engine.
+you can customize it according to your country region."
+  :type 'string
+  :group 'go-translate)
+
+\f
+;;; Components
+
+(defclass gts-google-rpc-parser (gts-google-parser)
+  ((tag :initform "Detail")))
+
+(defclass gts-google-rpc-summary-parser (gts-google-summary-parser gts-google-rpc-parser)
+  ((tag :initform "Summary")))
+
+(defclass gts-google-rpc-engine (gts-engine)
+  ((tag       :initform "Google/RPC")
+   (sub-url   :initform "/_/TranslateWebserverUi/data/batchexecute")
+   (parser    :initform (gts-google-rpc-parser))
+
+   (rpc-translate :initform "MkEWBc")
+   (rpc-tts       :initform "jQ1olc")
+   (rpc-sid       :initform "FdrFJe")
+   (rpc-bl        :initform "cfb2h")))
+
+\f
+;;; Engine
+
+(defvar gts-google-rpc-request-headers '(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8")))
+
+(cl-defmethod gts-with-token ((engine gts-google-rpc-engine) done fail)
+  (with-slots (sub-url rpc-sid rpc-bl) engine
+    (gts-do-request gts-google-rpc-base-url
+                    :done
+                    (lambda ()
+                      (let* ((f-sid (progn
+                                      (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-sid))
+                                      (match-string 1)))
+                             (bl (progn (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-bl))
+                                        (match-string 1)))
+                             (url-tpl (lambda (rpcid lang)
+                                        (format "%s%s?%s" gts-google-rpc-base-url sub-url
+                                                (gts-format-params
+                                                 `(("rpcids"       . ,rpcid)
+                                                   ("f.sid"        . ,f-sid)
+                                                   ("bl"           . ,bl)
+                                                   ("hl"           . ,lang)
+                                                   ("soc-app"      . 1)
+                                                   ("soc-platform" . 1)
+                                                   ("soc-device"   . 1)
+                                                   ("_reqid"       . ,(+ 1000 (random 9000)))
+                                                   ("rt"           . "c")))))))
+                        (funcall done url-tpl)))
+                    :fail fail)))
+
+(cl-defmethod gts-translate ((engine gts-google-rpc-engine) task rendercb)
+  (gts-with-token engine
+    (lambda (url-tpl)
+      (with-slots (text from to) task
+        (with-slots (rpc-translate parser) engine
+          (gts-do-request (funcall url-tpl rpc-translate to)
+                          :headers gts-google-rpc-request-headers
+                          :data (format
+                                 "f.req=%s&"
+                                 (url-hexify-string
+                                  (let ((outer [[[rpcid inner nil "generic"]]])
+                                        (inner [[text from to t][nil]]))
+                                    (setf (aref (aref inner 0) 0) text)
+                                    (setf (aref (aref inner 0) 1) from)
+                                    (setf (aref (aref inner 0) 2) to)
+                                    (setf (aref (aref (aref outer 0) 0) 0) rpc-translate)
+                                    (setf (aref (aref (aref outer 0) 0) 1) (json-encode inner))
+                                    (json-encode outer))))
+                          :done (lambda ()
+                                  (gts-update-raw task (buffer-string))
+                                  (gts-parse parser task)
+                                  (funcall rendercb))
+                          :fail (lambda (err)
+                                  (gts-render-fail task err))))))
+    (lambda (err)
+      (gts-render-fail task
+        (format "Error occurred when request for token.\n\n%s" err)))))
+
+(cl-defmethod gts-tts ((engine gts-google-rpc-engine) text lang)
+  (gts-with-token engine
+    (lambda (url-tpl)
+      (with-slots (rpc-tts) engine
+        (gts-do-request (funcall url-tpl rpc-tts "en-US")
+                        :headers gts-google-rpc-request-headers
+                        :data (format
+                               "f.req=%s&"
+                               (url-hexify-string
+                                (let ((outer [[[rpcid inner nil "generic"]]])
+                                      (inner [text lang nil nil]))
+                                  (setf (aref inner 0) text)
+                                  (setf (aref inner 1) lang)
+                                  (setf (aref (aref (aref outer 0) 0) 0) rpc-tts)
+                                  (setf (aref (aref (aref outer 0) 0) 1) (json-encode inner))
+                                  (json-encode outer))))
+                        :done (lambda ()
+                                (let (beg end json code)
+                                  (re-search-forward "^[0-9]+$")
+                                  (setq beg (point))
+                                  (re-search-forward "^\\([0-9]+\\)$")
+                                  (setq end (- (point) (length (match-string 1))))
+                                  (setq json (json-read-from-string (string-trim (buffer-substring-no-properties beg end))))
+                                  (when (string= (gts-aref-for json 0 0) "wrb.fr")
+                                    (setq code (aref (json-read-from-string (gts-aref-for json 0 2)) 0))
+                                    (erase-buffer)
+                                    (insert code)
+                                    (base64-decode-region (point-min) (point-max))
+                                    (gts-tts-speak-buffer-data))))
+                        :fail (lambda (err) (user-error "Error when TTS. %s" err)))))
+    (lambda (_) (user-error "Error occurred when request for token"))))
+
+\f
+;;; Parser
+
+(cl-defmethod gts-resp-to-json ((_ gts-google-rpc-parser) resp)
+  "Convert the buffer RESP into JSON format."
+  (condition-case err
+      (with-temp-buffer
+        (let (beg end str json)
+          (insert resp)
+          (goto-char (point-min))
+          (re-search-forward "^[0-9]+$")
+          (setq beg (point))
+          (re-search-forward "^\\([0-9]+\\)$")
+          (setq end (- (point) (length (match-string 1))))
+          (setq str
+                (decode-coding-string
+                 (string-trim (buffer-substring-no-properties beg end))
+                 'utf-8))
+          (setq json (json-read-from-string str))
+          (if (string= (aref (aref json 0) 0) "wrb.fr")
+              (let ((json-str (aref (aref json 0) 2)))
+                (json-read-from-string json-str))
+            (error "No results found"))))
+    (error (user-error "Result conversion error (%s)" err))))
+
+(cl-defmethod gts-result--brief ((_ gts-google-rpc-parser) json)
+  "Get the translation text from JSON."
+  (mapconcat
+   (lambda (item)
+     (if (ignore-errors (aref item 2))
+         (format " %s" (aref item 0))
+       (aref item 0)))
+   (gts-aref-for json 1 0 0 5)
+   ""))
+
+(cl-defmethod gts-result--sphonetic ((_ gts-google-rpc-parser) json)
+  "Get the text phonetic from JSON."
+  (gts-aref-for json 0 0))
+
+(cl-defmethod gts-result--tphonetic ((_ gts-google-rpc-parser) json)
+  "Get the translation phonetic from JSON."
+  (gts-aref-for json 1 0 0 1))
+
+(cl-defmethod gts-result--details ((_ gts-google-rpc-parser) json)
+  "Get the details from JSON.
+Result style: ((noun (a (x y z))) (verb (b (m n o))))."
+  (cl-loop with dts = (ignore-errors (gts-aref-for json 3 5 0))
+           for i across dts
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 1)
+                     collect
+                     (cons
+                      (aref j 0)
+                      (cl-loop for k across (aref j 2) collect k))))))
+
+(cl-defmethod gts-result--definitions ((_ gts-google-rpc-parser) json)
+  "Get the definitions from JSON.
+Result style: ((noun (a b)) (verb (c d)))."
+  (cl-loop with defs = (ignore-errors (gts-aref-for json 3 1 0))
+           for i across defs
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 1)
+                     collect
+                     (cons (aref j 0)
+                           (ignore-errors (aref j 1)))))))
+
+(cl-defmethod gts-result--suggestion ((_ gts-google-rpc-parser) json)
+  "Get the suggestion from JSON."
+  (ignore-errors
+    (replace-regexp-in-string "<b>\\|</b>\\|<i>\\|</i>" "" (gts-aref-for json 0 1 0 0 1))))
+
+
+(provide 'gts-engine-google-rpc)
+
+;;; gts-engine-google-rpc.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-google.el b/elpa/go-translate-20230503.303/gts-engine-google.el
new file mode 100644 (file)
index 0000000..2ac39ba
--- /dev/null
@@ -0,0 +1,424 @@
+;;; gts-engine-google.el --- Google translate module -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; http://translate.google.com
+
+;;; Code:
+
+(require 'gts-implements)
+
+\f
+;;; Components
+
+(defclass gts-google-parser (gts-parser)
+  ((tag :initform "Detail")))
+
+(defclass gts-google-summary-parser (gts-google-parser)
+  ((tag :initform "Summary")))
+
+(defclass gts-google-engine (gts-engine)
+  ((tag                :initform "Google")
+   (base-url           :initform "http://translate.googleapis.com")
+   (sub-url            :initform "/translate_a/single")
+   (token              :initform (cons 430675 2721866130) :initarg token) ; hard code
+   (token-time         :initform t)
+   (token-expired-time :initform (* 30 60))
+   (parser             :initform (gts-google-parser))))
+
+\f
+;;; Engine
+
+(defvar gts-google-request-headers '(("Connection" . "Keep-Alive")))
+
+(cl-defmethod gts-gen-url ((engine gts-google-engine) text from to)
+  "Generate the url with TEXT, FROM and TO. Return a (url text from to) list."
+  (format "%s%s?%s"
+          (oref engine base-url)
+          (oref engine sub-url)
+          (mapconcat (lambda (p)
+                       (format "%s=%s" (url-hexify-string (car p)) (url-hexify-string (cdr p))))
+                     `(("client" . "gtx")
+                       ("ie"     . "UTF-8")
+                       ("oe"     . "UTF-8")
+                       ("dt"     . "bd")
+                       ("dt"     . "ex")
+                       ("dt"     . "ld")
+                       ("dt"     . "md")
+                       ("dt"     . "qc")
+                       ("dt"     . "rw")
+                       ("dt"     . "rm")
+                       ("dt"     . "ss")
+                       ("dt"     . "t")
+                       ("dt"     . "at")
+                       ("pc"     . "1")
+                       ("otf"    . "1")
+                       ("srcrom" . "1")
+                       ("ssel"   . "0")
+                       ("tsel"   . "0")
+                       ("hl"     . ,to)
+                       ("sl"     . ,from)
+                       ("tl"     . ,to)
+                       ("q"      . ,text)
+                       ("tk"     . ,(gts-google-tkk (oref engine token) text)))
+                     "&")))
+
+(cl-defmethod gts-token-available-p ((engine gts-google-engine))
+  (with-slots (token token-time token-expired-time) engine
+    (and token
+         (or (eq token-time t)
+             (and token-time
+                  (<= (float-time (time-subtract (current-time) token-time))
+                      token-expired-time))))))
+
+(cl-defmethod gts-with-token ((engine gts-google-engine) done fail)
+  (with-slots (token token-time base-url) engine
+    (if (gts-token-available-p engine)
+        (funcall done)
+      (gts-do-request base-url
+                      :headers gts-google-request-headers
+                      :done (lambda ()
+                              (let ((tk (progn
+                                          (re-search-forward ",tkk:'\\([0-9]+\\)\\.\\([0-9]+\\)")
+                                          (cons (string-to-number (match-string 1))
+                                                (string-to-number (match-string 2))))))
+                                (setf token tk)
+                                (setf token-time (current-time))
+                                (funcall done)))
+                      :fail fail))))
+
+(cl-defmethod gts-translate ((engine gts-google-engine) task rendercb)
+  (gts-with-token engine
+    (lambda ()
+      (with-slots (text from to) task
+        (gts-do-request (gts-gen-url engine text from to)
+                        :headers gts-google-request-headers
+                        :done (lambda ()
+                                (gts-update-raw task (buffer-string))
+                                (gts-parse (oref engine parser) task)
+                                (funcall rendercb))
+                        :fail (lambda (err)
+                                (gts-render-fail task err)))))
+    (lambda (err)
+      (gts-render-fail task
+        (format "Error when fetching Token-Key, check your network and proxy, or retry later\n\n%s" err)))))
+
+;; tts
+
+(cl-defmethod gts-tts-gen-urls ((engine gts-google-engine) text lang)
+  "Generate the tts urls for TEXT to LANGUAGE."
+  (cl-loop with texts = (gts-tts-text-splitter engine text)
+           for total = (length texts)
+           for index from 0
+           for piece in texts
+           collect
+           (let ((params `(("ie"      . "UTF-8")
+                           ("client"  . "gtx")
+                           ("prev"    . "input")
+                           ("q"       . ,text)
+                           ("tl"      . ,lang)
+                           ("total"   . ,(number-to-string total))
+                           ("idx"     . ,(number-to-string index))
+                           ("textlen" . ,(number-to-string (length piece)))
+                           ("tk"      . ,(gts-google-tkk (oref engine token) piece)))))
+             (format "%s/translate_tts?%s"
+                     (oref engine base-url)
+                     (mapconcat (lambda (p)
+                                  (format "%s=%s"
+                                          (url-hexify-string (car p))
+                                          (url-hexify-string (cdr p))))
+                                params "&")))))
+
+(cl-defmethod gts-tts-text-splitter ((_ gts-google-engine) text)
+  "Split TEXT by maxlen at applicable point for translating.
+Code from `google-translate', maybe improve it someday."
+  (let (result (maxlen 200))
+    (if (or (null maxlen) (<= maxlen 0))
+           (push text result)
+      ;; split long text?
+      (with-temp-buffer
+           (save-excursion (insert text))
+           ;; strategy to split at applicable point
+           ;; 1) fill-region remaining text by maxlen
+           ;; 2) find end of sentence, end of punctuation, word boundary
+           ;; 3) consume from remaining text between start and (2)
+           ;; 4) repeat
+           (let ((fill-column (* maxlen 3))
+                 (sentence-end-double-space nil)
+                 (pos (point-min)))
+             (while (< pos (point-max))
+               (save-restriction
+                 (narrow-to-region pos (point-max))
+                 (fill-region pos (point-max))
+                 (let ((limit (+ pos maxlen)))
+                       (if (>= limit (point-max))
+                           (setq limit (point-max))
+                         (goto-char limit)
+                         ;; try to split at end of sentence
+                         (if (> (backward-sentence) pos)
+                             (setq limit (point))
+                           ;; try to split at end of punctuation
+                           (goto-char limit)
+                           (if (re-search-backward "[,、]" pos t)
+                                   (setq limit (1+ (point))) ; include punctuation
+                             (goto-char limit)
+                             ;; try to split at word boundary
+                             (forward-word-strictly -1)
+                             (when (> (point) pos)
+                                   (setq limit (point))))))
+                       (push (buffer-substring-no-properties pos limit) result)
+                       (goto-char limit)
+                       (setq pos limit)))))))
+    (reverse result)))
+
+(cl-defmethod gts-tts ((engine gts-google-engine) text lang)
+  (let ((urls (gts-tts-gen-urls engine text lang))
+        (speaker (split-string gts-tts-speaker)))
+    (with-temp-message "Speaking..."
+      (gts-tts-try-interrupt-playing-process)
+      (apply #'call-process (car speaker) nil nil nil (append (cdr speaker) urls)))))
+
+\f
+;;; Parser
+
+;; detail-mode, use as default
+
+(cl-defmethod gts-parse ((parser gts-google-parser) task)
+  (let* ((json        (gts-resp-to-json parser (oref task raw)))
+         (brief       (gts-result--brief parser json))
+         (sphonetic   (gts-result--sphonetic parser json))
+         (tphonetic   (gts-result--tphonetic parser json))
+         (details     (gts-result--details parser json))
+         (definitions (gts-result--definitions parser json))
+         (suggestion  (gts-result--suggestion parser json))
+         (suggestionp (> (length suggestion) 0)) tbeg tend)
+    (cl-flet ((phonetic (ph)
+                (if (and (or definitions definitions) (> (length ph) 0))
+                    (propertize (format " [%s]" ph) 'face 'gts-google-buffer-phonetic-face)
+                  ""))
+              (headline (line)
+                (propertize (format "[%s]\n" line) 'face 'gts-google-buffer-headline-face)))
+      (with-temp-buffer
+        ;; suggestion
+        (when suggestionp
+          (insert (propertize "Do you mean:" 'face 'gts-google-buffer-suggestion-desc-face) " "
+                  (propertize suggestion 'face 'gts-google-buffer-suggestion-text-face) "?\n\n"))
+        ;; phonetic & translate
+        (if (or details definitions)
+            (progn
+              (insert (if suggestionp suggestion (oref (oref task translator) text)))
+              (insert (phonetic sphonetic) " ")
+              (setq tbeg (point))
+              (insert (propertize brief 'face 'gts-google-buffer-brief-result-face))
+              (setq tend (point))
+              (insert (phonetic tphonetic) "\n\n"))
+          (insert brief))
+        ;; details
+        (when details
+          (insert (headline "Details"))
+          (cl-loop for (label . items) in details
+                   unless (= 0 (length label))
+                   do (insert (format "\n%s:\n" label))
+                   do (cl-loop with index = 0
+                               for trans in items
+                               do (insert
+                                   (format "%2d. " (cl-incf index))
+                                   (car trans)
+                                   " (" (mapconcat #'identity (cdr trans) ", ")  ")"
+                                   "\n")))
+          (insert "\n"))
+        ;; definitions
+        (when definitions
+          (insert (headline "Definitions"))
+          (cl-loop for (label . items) in definitions
+                   unless (= 0 (length label))
+                   do (insert (format "\n%s:\n" label))
+                   do (cl-loop with index = 0
+                               for (exp . eg) in items
+                               do (insert (format "%2d. " (cl-incf index)) exp)
+                               when (> (length eg) 0)
+                               do (insert
+                                   "\n    > "
+                                   (propertize (or eg "") 'face 'gts-google-buffer-detail-demo-face))
+                               do (insert "\n"))))
+        ;; at last, fill and return
+        (gts-update-parsed task (buffer-string) (list :tbeg tbeg :tend tend))))))
+
+;; summary-mode
+
+(cl-defmethod gts-parse ((parser gts-google-summary-parser) task)
+  (let* ((json (gts-resp-to-json parser (oref task raw)))
+         (result (string-trim (gts-result--brief parser json))))
+    (gts-update-parsed task result (list :tbeg 1 :tend (+ 1 (length result))))))
+
+;; Extract results from response
+
+(cl-defmethod gts-resp-to-json ((_ gts-google-parser) resp)
+  "Convert the buffer RESP into JSON."
+  (condition-case err
+      (json-read-from-string (decode-coding-string resp 'utf-8))
+    (error (user-error "Result conversion error: %s" err))))
+
+(cl-defmethod gts-result--brief ((_ gts-google-parser) json)
+  "Get the translation text from JSON."
+  (mapconcat (lambda (item) (aref item 0)) (aref json 0) ""))
+
+(cl-defmethod gts-result--sphonetic ((_ gts-google-parser) json)
+  "Get the text phonetic from JSON."
+  (mapconcat (lambda (item) (if (> (length item) 3) (aref item 3) "")) (aref json 0) ""))
+
+(cl-defmethod gts-result--tphonetic ((_ gts-google-parser) json)
+  "Get the translation phonetic from JSON."
+  (mapconcat (lambda (item) (if (> (length item) 2) (aref item 2) "")) (aref json 0) ""))
+
+(cl-defmethod gts-result--details ((_ gts-google-parser) json)
+  "Get the details from JSON.
+Result style: ((noun (a (x y z))) (verb (b (m n o))))."
+  (cl-loop for i across (aref json 1)
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 2)
+                     collect
+                     (cons
+                      (aref j 0)
+                      (cl-loop for k across (aref j 1) collect k))))))
+
+(cl-defmethod gts-result--definitions ((_ gts-google-parser) json)
+  "Get the definitions from JSON.
+Result style: ((noun (a b)) (verb (c d)))."
+  (cl-loop with defs = (ignore-errors (aref json 12))
+           for i across defs
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 1)
+                     collect
+                     (cons
+                      (aref j 0)
+                      (ignore-errors (aref j 2)))))))
+
+(cl-defmethod gts-result--suggestion ((_ gts-google-parser) json)
+  "Get the suggestion from JSON."
+  (let ((info (aref json 7))) (unless (seq-empty-p info) (aref info 1))))
+
+\f
+;;; Token Key algorithm (deprecated)
+
+;; This algorithm is from `google-translate' project.
+;; https://github.com/atykhonov/google-translate/blob/master/google-translate-tk.el
+
+(defvar gts-google-token--bit-v-len 32)
+
+(defun gts-google-token--bit-v-2comp (v)
+  "Return the two's complement of V."
+  (let* ((vc (vconcat v))
+         (len (length vc)))
+    ;; Complement of v
+    (cl-loop for i from 0 below len do
+             (aset vc i (logxor (aref vc i) 1)))
+    ;; vc = complement of v + 1
+    (cl-loop for i downfrom (1- len) to 0
+             do (aset vc i (logxor (aref vc i) 1))
+             when (> (aref vc i) 0) return nil)
+    vc))
+
+(defun gts-google-token--number-to-bit-v (n)
+  "Return a bit vector from N."
+  (if (< n 0) (gts-google-token--bit-v-2comp
+               (gts-google-token--number-to-bit-v (abs n)))
+    (let ((v (make-vector gts-google-token--bit-v-len 0)))
+      (cl-loop for i downfrom (1- gts-google-token--bit-v-len) to 0
+               with q
+               when (< n 1) return nil do
+               (setq q (ffloor (* n 0.5)))
+               (aset v i (floor (- n (* 2.0 q))))
+               (setq n q))
+      v)))
+
+(defun gts-google-token--bit-v-to-number (v)
+  "Return a floating-point number from V."
+  (if (and (> (aref v 0) 0)
+           ;; Exclude [1 0 ... 0]
+           (cl-loop for i from 1 below gts-google-token--bit-v-len
+                    thereis (> (aref v i) 0)))
+      (- (gts-google-token--bit-v-to-number (gts-google-token--bit-v-2comp v)))
+    (funcall (if (> (aref v 0) 0)  #'- #'+)
+             (cl-reduce (lambda (acc e) (+ (* acc 2.0) e))
+                        v :initial-value 0.0))))
+
+(defun gts-google-token--logfn (fn n1 n2)
+  "Helper function for logical FN with N1 and N2."
+  (let ((v1 (gts-google-token--number-to-bit-v n1))
+        (v2 (gts-google-token--number-to-bit-v n2))
+        (v (make-vector gts-google-token--bit-v-len 0)))
+    (cl-loop for i from 0 below gts-google-token--bit-v-len do
+             (aset v i (funcall fn (aref v1 i) (aref v2 i))))
+    (gts-google-token--bit-v-to-number v)))
+
+(defun gts-google-token--logand (n1 n2)
+  "Return a floating-point number from N1 and N2."
+  (gts-google-token--logfn #'logand n1 n2))
+
+(defun gts-google-token--logxor (n1 n2)
+  "Return a floating-point number from N1 and N2."
+  (gts-google-token--logfn #'logxor n1 n2))
+
+(defun gts-google-token--lsh (n d)
+  "Return a floating-point number.
+Shift the bits in N to the left or rihgt D places.
+D is an integer."
+  (let ((v (gts-google-token--number-to-bit-v n))
+        (v-result (make-vector gts-google-token--bit-v-len 0)))
+    (if (< d 0)
+        ;; Shift Right Logical
+        (cl-loop for i from (abs d) below gts-google-token--bit-v-len
+                 for j from 0 do
+                 (aset v-result i (aref v j)))
+      ;; Shift Left Logical
+      (cl-loop for i from d below gts-google-token--bit-v-len
+               for j from 0 do
+               (aset v-result j (aref v i))))
+    (gts-google-token--bit-v-to-number v-result)))
+
+(defun gts-google-token--gen-rl (a b)
+  "Gen rl from A and B."
+  (cl-loop for c from 0 below (- (length b) 2) by 3
+           for d = (aref b (+ c 2)) do
+           (setq d (if (>= d ?a) (- d 87) (- d ?0)))
+           (setq d (if (= (aref b (1+ c)) ?+)
+                       (gts-google-token--lsh a (- d))
+                     (gts-google-token--lsh a d)))
+           (setq a (if (= (aref b c) ?+)
+                       (gts-google-token--logand (+ a d) 4294967295.0)
+                     (gts-google-token--logxor a d))))
+  a)
+
+(defun gts-google-tkk (token text)
+  "Calculate the Token for search TEXT.
+
+It will use the tkk from Google translate page."
+  (let* ((b (car token))
+         (d1 (cdr token))
+         (ub "+-3^+b+-f")
+         (vb "+-a^+6")
+         (a (cl-loop for c across (encode-coding-string text 'utf-8)
+                     for rr = (gts-google-token--gen-rl (+ (or rr b) c) vb)
+                     finally (return rr))))
+    (setq a (gts-google-token--gen-rl a ub))
+    (setq a (gts-google-token--logxor a d1))
+    (if (< a 0) (setq a (+ (gts-google-token--logand a 2147483647.0) 2147483648.0)))
+    (setq a (ffloor (mod a 1e6)))
+    (format "%s.%s"
+            (car (split-string (number-to-string a) "\\."))
+            (car (split-string (number-to-string
+                                (gts-google-token--logxor a b))
+                               "\\.")))))
+
+
+(provide 'gts-engine-google)
+
+;;; gts-engine-google.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-stardict.el b/elpa/go-translate-20230503.303/gts-engine-stardict.el
new file mode 100644 (file)
index 0000000..3ce059a
--- /dev/null
@@ -0,0 +1,64 @@
+;;; gts-engine-stardict.el --- StarDict -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; Engine for sdcv (StarDict Console Version).
+;;
+;; sdcv is an offline translate software. It can be used when no network.
+;;
+;; Make sure sdcv is installed on your system:
+;;
+;;   sudo pacman -S sdcv
+;;
+;; Then download dictionary data to ~/.stardict/dic or /usr/share/stardict/dic:
+;;
+;;   # For example from http://download.huzheng.org:
+;;
+;;   mkdir -p ~/.stardict/dic
+;;   cd ~/.stardict/dic
+;;   wget http://download.huzheng.org/zh_CN/stardict-langdao-ce-gb-2.4.2.tar.bz2
+;;   wget http://download.huzheng.org/zh_CN/stardict-langdao-ce-gb-2.4.2.tar.bz2
+;;   tar xvf stardict-langdao-ec-gb-2.4.2.tar.bz2
+;;   tar xvf stardict-langdao-ce-gb-2.4.2.tar.bz2
+;;   sdcv -l
+;;
+;; TODO: Provide more options and a better parser.
+
+;;; Code:
+
+(require 'gts-implements)
+
+(defclass gts-stardict-engine (gts-engine)
+  ((tag      :initform "StarDict")
+   (parser   :initform (gts-parser))))
+
+(defcustom gts-stardict-program "sdcv"
+  "Executable command of sdcv (StarDict Console Version)."
+  :type 'string
+  :group 'go-translate)
+
+(defcustom gts-stardict-args '("-n" "-c" "-0" "-1")
+  "Arguments passed to command `sdcv'."
+  :type 'list
+  :group 'go-translate)
+
+(cl-defmethod gts-translate ((engine gts-stardict-engine) task rendercb)
+  (let* ((text (oref task text))
+         (parser (oref engine parser))
+         (result (with-temp-buffer
+                   (require 'ansi-color)
+                   (apply #'call-process gts-stardict-program nil t nil
+                          (append gts-stardict-args (list text))) ;--json-output
+                   (if (string-equal (buffer-substring 1 16) "Nothing similar")
+                       "No translation result found, sorry :("
+                     (ansi-color-apply (buffer-string))))))
+    (gts-update-raw task result)
+    (gts-parse parser task)
+    (funcall rendercb)))
+
+(provide 'gts-engine-stardict)
+
+;;; gts-engine-stardict.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-youdao.el b/elpa/go-translate-20230503.303/gts-engine-youdao.el
new file mode 100644 (file)
index 0000000..594d345
--- /dev/null
@@ -0,0 +1,58 @@
+;;; gts-engine-youdao.el --- YouDao Dict -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; 有道词典 (dict.youdao.com), A chinese translation website.
+;;
+;; This engine is only for chinese user.
+;; > 此引擎仅供中文翻译使用
+;;
+;; Simply implement using EWW to do the parsing job for now. I am too lazy to parse and extract the html results.
+;; > 懒得去解析、组装 html,所以暂时直接借用 eww 的解析结果
+;; > 权作应急使用,*也许* 以后会增加更完善的 parser 方案
+
+;;; Code:
+
+(require 'gts-implements)
+
+(defclass gts-youdao-dict-eww-parser (gts-parser)
+  ((tag     :initform "EWW")))
+
+(defclass gts-youdao-dict-engine (gts-engine)
+  ((tag     :initform "有道词典")
+   (url     :initform "https://dict.youdao.com/result?word=%s&lang=%s")
+   (parser  :initform (gts-youdao-dict-eww-parser))))
+
+(cl-defmethod gts-translate ((engine gts-youdao-dict-engine) task rendercb)
+  (with-slots (text from to meta) task
+    (with-slots (url parser) engine
+      (let* ((lang (cond ((string-equal from "zh") to)
+                         ((string-equal to "zh") from)
+                         (t (user-error "只支持中文跟其他语言之间的翻译"))))
+             (url (format url (url-hexify-string text) (url-hexify-string lang))))
+        (setf meta (list :url url))
+        (gts-do-request url
+                        :done (lambda ()
+                                (gts-update-raw task (buffer-string)) ; html format response
+                                (gts-parse parser task)
+                                (funcall rendercb))
+                        :fail (lambda (err)
+                                (gts-render-fail task err)))))))
+
+(cl-defmethod gts-parse ((_ gts-youdao-dict-eww-parser) task)
+  (with-slots (raw meta) task
+    (with-temp-buffer
+      (let ((buf (current-buffer)))
+        (with-temp-buffer
+          (require 'eww)
+          (insert "Content-type: text/html; charset=utf-8\n\n" raw)
+          (eww-render nil (plist-get meta :url) nil buf))
+        (gts-update-parsed task (buffer-string) meta)))))
+
+
+(provide 'gts-engine-youdao)
+
+;;; gts-engine-youdao.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-faces.el b/elpa/go-translate-20230503.303/gts-faces.el
new file mode 100644 (file)
index 0000000..107b4ed
--- /dev/null
@@ -0,0 +1,124 @@
+;;; gts-faces.el --- Face definitions -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; The faces and colors.
+
+;;; Code:
+
+(require 'gts-core)
+
+(defgroup go-translate-faces nil
+  "The faces for this framework."
+  :group 'go-translate)
+
+\f
+;;; Common
+
+(defface gts-logger-buffer-tag-face '((t :inherit font-lock-comment-face))
+  "Used in the logger buffer tag keyword."
+  :group 'go-translate-faces)
+
+(defface gts-logger-buffer-timestamp-face '((t :foreground "grey"))
+  "Used in the logger buffer timestamp keyword."
+  :group 'go-translate-faces)
+
+(defface gts-render-buffer-header-line-lang-face '((t :inherit font-lock-keyword-face))
+  "Used in the buffer's header-line-format, from/to."
+  :group 'go-translate-faces)
+
+(defface gts-render-buffer-header-line-desc-face '((t :inherit font-lock-variable-name-face))
+  "Used in the buffer's header-line-format, description."
+  :group 'go-translate-faces)
+
+(defface gts-render-buffer-error-face '((t :inherit font-lock-warning-face))
+  "Used in the buffer's error text."
+  :group 'go-translate-faces)
+
+(defface gts-render-buffer-source-face '((t :inherit font-lock-string-face))
+  "Used in the buffer's source."
+  :group 'go-translate-faces)
+
+(defface gts-render-buffer-me-header-backgroud-face '((t :background "slategray" :foreground "white" :extend t))
+  "Used in the buffer's multiple engine separator."
+  :group 'go-translate-faces)
+
+\f
+;;; Google Buffer Render
+
+(defface gts-google-buffer-headline-face '((t :inherit font-lock-function-name-face :weight bold))
+  "Propertize the headline in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gts-google-buffer-phonetic-face '((t :inherit font-lock-string-face :slant normal))
+  "Propertize the phonetic in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gts-google-buffer-suggestion-desc-face '((t :inherit font-lock-warning-face))
+  "Propertize the suggestion description in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gts-google-buffer-suggestion-text-face '((t :slant italic :underline t))
+  "Propertize the phonetic text in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gts-google-buffer-brief-result-face '((t :weight bold))
+  "Propertize the brief result in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gts-google-buffer-source-face '((t :inherit font-lock-string-face))
+  "Propertize the source in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gts-google-buffer-detail-demo-face '((t :inherit font-lock-doc-face))
+  "Propertize the detail demo in buffer rendering."
+  :group 'go-translate-faces)
+
+\f
+;;; Posframe Render
+
+(defcustom gts-pop-posframe-forecolor "white"
+  "Default foreground color of pop posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gts-pop-posframe-backcolor "black"
+  "Default background color of pop posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gts-pin-posframe-forecolor nil
+  "Default foreground color of pin posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gts-pin-posframe-backcolor nil
+  "Default background color of pin posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gts-pin-posframe-bdcolor "#000000"
+  "Default border color of pin posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gts-pin-posframe-fringe-color nil
+  "Used in the posframe pip buffer fringe color."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defface gts-pop-posframe-me-header-face '((t :foreground "grey"))
+  "Used in the posframe pop buffer engine header."
+  :group 'go-translate-faces)
+
+(defface gts-pop-posframe-me-header-2-face '((t (:foreground "#666666" :underline t :extend t)))
+  "Used in the posframe pop buffer engine header."
+  :group 'go-translate-faces)
+
+
+(provide 'gts-faces)
+
+;;; gts-faces.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-implements.el b/elpa/go-translate-20230503.303/gts-implements.el
new file mode 100644 (file)
index 0000000..b5ac3bf
--- /dev/null
@@ -0,0 +1,661 @@
+;;; gts-implements.el --- Component implements -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
+;; SPDX-License-Identifier: MIT
+
+;;; Commentary:
+
+;; Builtin implements of basic components
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'url)
+(require 'json)
+(require 'gts-core)
+(require 'gts-faces)
+
+\f
+;;; [Logger] Log all to a standalone buffer
+
+(defclass gts-buffer-logger (gts-logger)
+  ((buffer :initarg buf :initform nil))) ; dont set :initarg to buffer, it will make an error on emacsv27.
+
+(defvar gts-logger-buffer-name " *gts-logger*")
+
+(cl-defmethod gts-log ((logger gts-buffer-logger) tag msg)
+  (with-current-buffer (get-buffer-create (or (oref logger buffer) gts-logger-buffer-name))
+    (goto-char (point-max))
+    (if (or (= (length msg) 0) (string= msg "\n"))
+        (insert "")
+      (insert
+       (propertize (cl-subseq (format "%-.1f" (time-to-seconds)) 6) 'face 'gts-logger-buffer-timestamp-face)
+       " "
+       (propertize (concat "[" tag "] ") 'face 'gts-logger-buffer-tag-face))
+      (insert msg))
+    (insert "\n")))
+
+(setq gts-default-logger (gts-buffer-logger))
+
+\f
+;;; [Cacher] Cache in the memory
+
+(defclass gts-memory-cacher (gts-cacher) ()
+  "Cache in the memory.")
+
+(cl-defmethod gts-cache-get ((cacher gts-memory-cacher) task)
+  (when gts-cache-enable
+    (with-slots (caches expired) cacher
+      (let ((key (gts-cache-key cacher task)))
+        (when-let ((cache (assoc key caches)))
+          (when (> (cddr cache) (time-to-seconds))
+            (gts-do-log 'memory-cacher (format "%s: get from cache %s (%s)" (oref task id) key (length caches)))
+            (cadr cache)))))))
+
+(cl-defmethod gts-cache-set ((cacher gts-memory-cacher) task result)
+  (when gts-cache-enable
+    (with-slots (caches expired) cacher
+      (let* ((key (gts-cache-key cacher task))
+             (cache (assoc key caches))
+             (text (oref task text))
+             (etime
+              ;; sentence with shorter expired time, but word with longer.
+              (if (string-match-p "[[:space:]\n]" text)
+                  (+ (time-to-seconds) gts-cache-expired-factor)
+                (+ (time-to-seconds) (* 100 gts-cache-expired-factor)))))
+        (if cache
+            (progn
+              (setf (cadr cache) result)
+              (setf (cddr cache) etime)
+              (gts-do-log 'memory-cacher (format "%s: update cache %s (%s)" (oref task id) key (length caches))))
+          (oset cacher caches (cons (cons key (cons result etime)) caches))
+          (gts-do-log 'memory-cacher (format "%s: add to cache %s (%s)" (oref task id) key (length caches))))
+        (gts-clear-expired cacher)))))
+
+(setq gts-default-cacher (gts-memory-cacher))
+
+\f
+;;; [Http Client] implement with builtin `url.el'.
+;; This will be used as the default http client.
+
+(defclass gts-url-http-client (gts-http-client) ())
+
+(defcustom gts-user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36"
+  "The user agent used when send a request."
+  :type 'string
+  :group 'go-translate)
+
+(defvar url-http-end-of-headers)
+
+(cl-defmethod gts-request ((_ gts-url-http-client) url &key done fail data headers)
+  "Request URL with DATA and HEADERS asynchronous.
+Execute DONE when success, or FAIL when failed."
+  (cl-assert (and url done fail))
+  (let ((url-debug gts-debug-p)
+        (url-user-agent gts-user-agent)
+        (url-request-extra-headers headers)
+        (url-request-method (if data "POST" "GET"))
+        (url-request-data data))
+    (url-retrieve url (lambda (status)
+                        (unwind-protect
+                            (if-let ((err (cond
+                                           ((eq (car status) :error) (cadr status))
+                                           ((null url-http-end-of-headers) "Nothing responsed from server"))))
+                                (funcall fail err)
+                              (when done
+                                (delete-region (point-min) url-http-end-of-headers)
+                                (funcall done)))
+                          (kill-buffer)))
+                  nil t)))
+
+(setq gts-default-http-client (gts-url-http-client)) ; use this as default
+
+\f
+;;; [Render] buffer render
+
+(defclass gts-buffer-render (gts-render) ())
+
+(defcustom gts-split-width-threshold 80
+  "Threshold width for window horizontal split."
+  :group 'go-translate
+  :type '(choice
+          (const :tag "Disable" nil)
+          (integer :tag "Threshold")))
+
+(defcustom gts-buffer-follow-p nil
+  "If t then pop to the result window after translation."
+  :type 'boolean
+  :group 'go-translate)
+
+(defcustom gts-buffer-name "*Go-Translate*"
+  "The name of translation result buffer."
+  :type 'string
+  :group 'go-translate)
+
+(defcustom gts-buffer-window-config nil
+  "Window configuration used by the result buffer window.
+
+For example, set to:
+
+ \\='((display-buffer-reuse-window display-buffer-in-side-window)
+   (side . right))
+
+will force opening in right side window."
+  :type 'list
+  :group 'go-translate)
+
+(defcustom gts-buffer-render-task-header-function #'gts-buffer-render-task-header
+  "Function used to render the task header when in multiple engines's query."
+  :type 'function
+  :group 'go-translate)
+
+(defvar-local gts-buffer-translator nil)
+(defvar-local gts-buffer-keybinding-messages nil)
+(defvar-local gts-buffer-local-map nil)
+
+(cl-defmacro gts-buffer-set-key ((key &optional desc) form)
+  "Helper for define KEY in buffer."
+  (declare (indent 1))
+  `(progn (define-key gts-buffer-local-map
+                      (kbd ,key)
+                      ,(if (equal 'function (car form)) `,form `(lambda () (interactive) ,form)))
+          (when ,desc
+            (cl-delete ,key gts-buffer-keybinding-messages :key #'car :test #'string=)
+            (push (cons ,key ,desc) gts-buffer-keybinding-messages))))
+
+(defun gts-buffer-init-header-line (path &optional desc)
+  "Setup header line format.
+It will show the basic information of the translation,
+including PATH and other DESC."
+  (setq header-line-format
+        (list
+         " "
+         "[" (propertize (car path) 'face 'gts-render-buffer-header-line-lang-face) "]"
+         (if desc (concat " ― " (propertize desc 'face 'gts-render-buffer-header-line-desc-face)) "")
+         " → "
+         "[" (propertize (cdr path) 'face 'gts-render-buffer-header-line-lang-face) "]"
+         "   (" (propertize "h" 'face 'font-lock-type-face) " for help)"
+         "          "
+         "Loading...")))
+
+(defun gts-buffer-change-header-line-state (type)
+  "Update header line according TYPE."
+  (cl-case type
+    (done
+     (when (string-equal (car (last header-line-format)) "Loading...")
+       (setq header-line-format (butlast header-line-format))))))
+
+(defun gts-buffer-display-or-focus-buffer (buffer)
+  "Only display the BUFFER, or display and focus."
+  (with-current-buffer buffer
+    (if gts-buffer-follow-p (pop-to-buffer buffer)
+      (display-buffer buffer))))
+
+(defun gts-buffer-ensure-a-blank-line-at-beginning (buffer)
+  "Ensure a blank line at beginning of BUFFER."
+  (with-current-buffer buffer
+    (let ((inhibit-read-only t))
+      (save-excursion
+        (let ((m (mark-marker)))
+          (goto-char (point-min))
+          (unless (equal (char-after) ?\n)
+            (insert "\n"))
+          (when (ignore-errors (= (marker-position m) 1))
+            (set-marker m (point))))
+        (set-buffer-modified-p nil)))))
+
+(defun gts-childframe-of-buffer (&optional buffer)
+  "Get the childframe of BUFFER if it exists, or return nil."
+  (let ((cf (window-frame (get-buffer-window buffer t))))
+    (when (frame-parameter cf 'parent-frame) cf)))
+
+(defun gts-buffer-render-task-header (engine-tag parser-tag)
+  (concat (propertize
+           (format "%s %s\n" engine-tag (if parser-tag (format "(%s) " parser-tag) ""))
+           'face 'gts-render-buffer-me-header-backgroud-face)))
+
+(defun gts-render-buffer-prepare (buffer translator)
+  (with-current-buffer (get-buffer-create buffer)
+    (let ((inhibit-read-only t)
+          (engines (gts-get translator 'engines)))
+      (with-slots (text path) translator
+        ;; setup
+        (deactivate-mark)
+        (visual-line-mode -1)
+        (font-lock-mode 1)
+        (setq-local cursor-type 'hbar)
+        (setq-local cursor-in-non-selected-windows nil)
+        (setq-local gts-buffer-translator translator)
+        ;; headline
+        (gts-buffer-init-header-line path (unless (cdr engines) (oref (car engines) tag)))
+        ;; source text
+        (erase-buffer)
+        (unless (gts-childframe-of-buffer (current-buffer)) (insert "\n")) ; except childframe
+        (insert text)
+        ;; keybinds
+        (setq gts-buffer-local-map (make-sparse-keymap))
+        (gts-buffer-set-key ("x" "Reverse Path")
+          (gts-translate translator text (cons (cdr path) (car path))))
+        (gts-buffer-set-key ("M-p" "Prev Path")
+          (gts-translate translator text (gts-next-path (gts-get translator 'picker) text path t)))
+        (gts-buffer-set-key ("M-n" "Next Path")
+          (gts-translate translator text (gts-next-path (gts-get translator 'picker) text path)))
+        (gts-buffer-set-key ("g" "Refresh")
+          (gts-translate translator text path))
+        (gts-buffer-set-key ("y" "TTS")
+          (if-let ((task (get-text-property (point) 'task)))
+              (gts-do-tts text (car path) (oref task engine))
+            (message "[TTS] No engine found at point")))
+        (gts-buffer-set-key ("O" "Open Externally")
+          (when-let ((task (get-text-property (point) 'task)))
+            (let* ((meta (oref task meta)) (url (plist-get meta :url)))
+              (cond (url (browse-url url) (message "Opening %s... Done!" url))
+                    (t (message "Don't know how to open externally for this."))))))
+        (gts-buffer-set-key ("C" "Clean Cache")
+          (gts-clear-all gts-default-cacher))
+        (gts-buffer-set-key ("t" "Toggle-Follow")
+          (let ((state (if (setq gts-buffer-follow-p (not gts-buffer-follow-p)) "allowed" "disabled")))
+            (message "Now, buffer following %s." state)))
+        (gts-buffer-set-key ("q" "Quit") #'kill-buffer-and-window)
+        (gts-buffer-set-key ("p") #'previous-line)
+        (gts-buffer-set-key ("n") #'next-line)
+        (gts-buffer-set-key ("C-g")
+          (unwind-protect (gts-tts-try-interrupt-playing-process)
+            (keyboard-quit)))
+        (gts-buffer-set-key ("h")
+          (message (mapconcat
+                    (lambda (kd) (concat (propertize (car kd) 'face 'font-lock-keyword-face) ": " (cdr kd)))
+                    (reverse gts-buffer-keybinding-messages) " ")))
+        (gts-buffer-set-key ("C-x C-q")
+          (progn (read-only-mode -1)
+                 (use-local-map nil)
+                 (local-set-key (kbd "C-x C-q")
+                                (lambda ()
+                                  (interactive)
+                                  (read-only-mode 1)
+                                  (use-local-map gts-buffer-local-map)))))
+        (read-only-mode 1)
+        (use-local-map gts-buffer-local-map)
+        ;; execute the hook if exists
+        (run-hooks 'gts-after-buffer-prepared-hook)))))
+
+(defun gts-render-buffer (buffer task)
+  "For single engine translation."
+  (when-let ((inhibit-read-only t)
+             (buf (get-buffer buffer)))
+    (with-current-buffer buf
+      (with-slots (err parsed meta engine translator) task
+        ;; error
+        (if err
+            (progn
+              (goto-char (point-max))
+              (insert (propertize (format "\n\n%s" err) 'face 'gts-render-buffer-error-face)))
+          (erase-buffer)
+          (cond
+           ;; content (non-split)
+           ((stringp parsed)
+            (let ((tbeg (plist-get meta :tbeg))
+                  (tend (plist-get meta :tend)))
+              (unless tbeg
+                (insert (propertize (oref translator text) 'face 'gts-render-buffer-source-face) "\n\n"))
+              (unless tbeg (setq tbeg (point)))
+              (insert parsed)
+              (unless tend (setq tend (point)))
+              ;; try set mark in beginning of the translate result,
+              ;; and set currsor position in end of the translate result,
+              ;; so you can quick select the translate result with C-x C-x.
+              (when (not (gts-childframe-of-buffer buf)) ; when not childframe
+                (push-mark tend 'nomsg)
+                (goto-char tbeg)
+                (set-window-point (get-buffer-window buf t) tbeg))))
+           ;; content (split)
+           (t (cl-loop for idx from 0
+                       for src in (gts-get translator 'sptext)
+                       for tar in parsed
+                       concat (concat (propertize src 'face 'gts-render-buffer-source-face) "\n\n" tar "\n\n") into res
+                       finally (insert res)))))
+        ;; update states
+        (set-buffer-modified-p nil)
+        (gts-buffer-change-header-line-state 'done)
+        (put-text-property (point-min) (point-max) 'task task)
+        ;; execute the hook if exists
+        (run-hooks 'gts-after-buffer-render-hook))
+      buf)))
+
+(defun gts-render-buffer-multi-engines (buffer task)
+  "For multiple engines translation."
+  (when-let ((inhibit-read-only t)
+             (buf (get-buffer buffer)))
+    (with-slots (from to translator) task
+      (with-slots (text task-queue version) translator
+        (with-current-buffer buf
+          (when (equal (oref gts-buffer-translator version) version)
+            (erase-buffer)
+            (insert (propertize (format "\n%s\n\n" text) 'face 'gts-render-buffer-source-face))
+            ;; content
+            (save-excursion
+              (dolist (task task-queue)
+                (with-slots (err parsed meta engine) task
+                  (let* ((result (if (or err (stringp parsed)) parsed
+                                   (string-join parsed "\n\n")))
+                         (header (funcall gts-buffer-render-task-header-function
+                                          (oref engine tag)
+                                          (oref (oref engine parser) tag)))
+                         (content (cond
+                                   (err
+                                    (concat header (propertize (format "\n%s\n\n" err) 'face 'gts-render-buffer-error-face)))
+                                   ((string-empty-p result)
+                                    (concat header "\nLoading...\n\n"))
+                                   (t (concat header "\n" (string-trim result) "\n\n")))))
+                    (put-text-property 0 (length content) 'task task content)
+                    (insert content)))))
+            ;; states
+            (set-buffer-modified-p nil)
+            ;; all tasks finished
+            (when (= (oref translator state) 3)
+              ;; cursor
+              (unless (gts-childframe-of-buffer buf)
+                (set-window-point (get-buffer-window buf t) (save-excursion (forward-line 2) (point))))
+              ;; state
+              (gts-buffer-change-header-line-state 'done))
+            ;; execute the hook if exists
+            (run-hooks 'gts-after-buffer-multiple-render-hook)))
+        buf))))
+
+(cl-defmethod gts-pre ((_ gts-buffer-render) translator)
+  ;; init and setup
+  (gts-render-buffer-prepare gts-buffer-name translator)
+  ;; display
+  (let ((split-width-threshold (or gts-split-width-threshold split-width-threshold)))
+    (display-buffer gts-buffer-name gts-buffer-window-config)))
+
+(cl-defmethod gts-out ((_ gts-buffer-render) task)
+  ;; render & display
+  (when-let ((buf (gts-render-buffer gts-buffer-name task)))
+    (gts-buffer-ensure-a-blank-line-at-beginning buf)
+    (gts-buffer-display-or-focus-buffer buf)))
+
+(cl-defmethod gts-me-out ((_ gts-buffer-render) task)
+  ;; render & display
+  (when-let ((buf (gts-render-buffer-multi-engines gts-buffer-name task)))
+    (when (= (oref (oref task translator) state) 3)
+      (gts-buffer-display-or-focus-buffer buf))))
+
+\f
+;;; [Render] Child-Frame Render (Popup Mode)
+;; implements via package Posframe, you should install it before use this
+
+(defclass gts-posframe-pop-render (gts-render)
+  ((width       :initarg :width        :initform 100)
+   (height      :initarg :height       :initform 15)
+   (forecolor   :initarg :forecolor    :initform nil)
+   (backcolor   :initarg :backcolor    :initform nil)
+   (padding     :initarg :padding      :initform 12))
+  "Pop up a childframe to show the result.
+The frame will disappear when do do anything but focus in it.
+Manually close the frame with `q'.")
+
+(require 'posframe nil t)
+(declare-function posframe-show "ext:posframe.el" t t)
+(declare-function posframe-delete "ext:posframe.el" t t)
+(declare-function posframe-hide "ext:posframe.el" t t)
+(declare-function posframe-refresh "ext:posframe.el" t t)
+(declare-function posframe-poshandler-frame-top-right-corner "ext:posframe.el" t t)
+
+(defcustom gts-posframe-pop-render-buffer " *GTS-Pop-Posframe*"
+  "Buffer name of Pop Posframe."
+  :type 'string
+  :group 'go-translate)
+
+(defvar gts-posframe-pop-render-timeout 30)
+(defvar gts-posframe-pop-render-poshandler nil)
+
+(defun gts-posframe-init-header-line (from to)
+  (setq header-line-format
+        (list "[" (propertize from 'face 'gts-render-buffer-header-line-lang-face) "]"
+              " → "
+              "[" (propertize to 'face 'gts-render-buffer-header-line-lang-face) "]")))
+
+(defun gts-posframe-render-auto-close-handler ()
+  "Close the pop-up posframe window."
+  (interactive)
+  (unless (or (and gts-current-command
+                   (member this-command (list gts-current-command #'exit-minibuffer)))
+              (and gts-posframe-pop-render-buffer
+                   (string= (buffer-name) gts-posframe-pop-render-buffer)))
+    (ignore-errors (posframe-delete gts-posframe-pop-render-buffer))
+    (remove-hook 'post-command-hook #'gts-posframe-render-auto-close-handler)))
+
+(cl-defmethod gts-pre ((render gts-posframe-pop-render) translator)
+  (with-slots (width height forecolor backcolor padding) render
+    (let ((inhibit-read-only t)
+          (buf gts-posframe-pop-render-buffer))
+      (posframe-show buf
+                     :string "Loading..."
+                     :timeout gts-posframe-pop-render-timeout
+                     :max-width width
+                     :max-height height
+                     :foreground-color (or forecolor gts-pop-posframe-forecolor)
+                     :background-color (or backcolor gts-pop-posframe-backcolor)
+                     :internal-border-width padding
+                     :internal-border-color (or backcolor gts-pop-posframe-backcolor)
+                     :accept-focus t
+                     :position (point)
+                     :poshandler gts-posframe-pop-render-poshandler)
+      ;; render
+      (gts-render-buffer-prepare buf translator)
+      (posframe-refresh buf)
+      ;; setup
+      (with-current-buffer buf
+        (gts-buffer-set-key ("q" "Close") (posframe-delete buf))))))
+
+(cl-defmethod gts-out ((_ gts-posframe-pop-render) task)
+  ;; render & refresh
+  (when-let ((buf (gts-render-buffer gts-posframe-pop-render-buffer task)))
+    (posframe-refresh buf)
+    (add-hook 'post-command-hook #'gts-posframe-render-auto-close-handler)))
+
+(cl-defmethod initialize-instance :after ((_ gts-posframe-pop-render) &rest _)
+  (unless (featurep 'posframe)
+    (user-error "To use `gts-posframe-render', you should install and load package `posframe' first")))
+
+\f
+;;; [Render] Child-Frame Render (Pin Mode)
+
+(defclass gts-posframe-pin-render (gts-posframe-pop-render)
+  ((width       :initarg :width        :initform 60)
+   (height      :initarg :height       :initform 20)
+   (padding     :initarg :padding      :initform 8)
+   (bd-width    :initarg :bd-width     :initform 1)
+   (bd-color    :initarg :bd-color     :initform nil)
+   (backcolor   :initarg :backcolor    :initform nil)
+   (fri-color   :initarg :fringe-color :initform nil)
+   (position    :initarg :position     :initform nil))
+  "Pin the childframe in a fixed position to display the translate result.
+The childframe will not close, until you kill it with `q'.
+Other operations in the childframe buffer, just like in 'gts-buffer-render'.")
+
+(defcustom gts-posframe-pin-render-buffer " *GTS-Pin-Posframe*"
+  "Buffer name of Pin Posframe."
+  :type 'string
+  :group 'go-translate)
+
+(defvar gts-posframe-pin-render-frame nil)
+(defvar gts-posframe-pin-render-poshandler #'posframe-poshandler-frame-top-right-corner)
+
+(cl-defmethod gts-pre ((render gts-posframe-pin-render) translator)
+  ;; create/show frame
+  (if (and (get-buffer gts-posframe-pin-render-buffer) gts-posframe-pin-render-frame)
+      (make-frame-visible gts-posframe-pin-render-frame)
+    (with-slots (width height min-width min-height bd-width forecolor backcolor bd-color padding position) render
+      (setq gts-posframe-pin-render-frame
+            (let ((inhibit-read-only t))
+              (posframe-show gts-posframe-pin-render-buffer
+                             :string "\nLoading..."
+                             :width width
+                             :height height
+                             :min-width width
+                             :min-height height
+                             :foreground-color (or forecolor gts-pin-posframe-forecolor)
+                             :background-color (or backcolor gts-pin-posframe-backcolor)
+                             :internal-border-width bd-width
+                             :border-color (or bd-color gts-pin-posframe-bdcolor)
+                             :left-fringe padding
+                             :right-fringe padding
+                             :refresh nil
+                             :accept-focus t
+                             :respect-header-line t
+                             :position position
+                             :poshandler (unless position gts-posframe-pin-render-poshandler)))))
+    (set-frame-parameter gts-posframe-pin-render-frame 'drag-internal-border t)
+    (set-frame-parameter gts-posframe-pin-render-frame 'drag-with-header-line t)
+    (when-let ((color (or (oref render fri-color) gts-pin-posframe-fringe-color)))
+      (set-face-background 'fringe color  gts-posframe-pin-render-frame)))
+  ;; render
+  (gts-render-buffer-prepare gts-posframe-pin-render-buffer translator)
+  (gts-buffer-ensure-a-blank-line-at-beginning gts-posframe-pin-render-buffer)
+  ;; setup
+  (with-current-buffer gts-posframe-pin-render-buffer
+    (gts-buffer-set-key ("q" "Hide") (posframe-hide gts-posframe-pin-render-buffer))
+    (gts-buffer-set-key ("Q" "Close") (posframe-delete gts-posframe-pin-render-buffer))))
+
+(cl-defmethod gts-out ((_ gts-posframe-pin-render) task)
+  ;; render & refresh
+  (when-let ((buf (gts-render-buffer gts-posframe-pin-render-buffer task)))
+    (gts-buffer-ensure-a-blank-line-at-beginning buf)))
+
+(cl-defmethod gts-me-out ((_ gts-posframe-pin-render) task)
+  ;; render & refresh
+  (gts-render-buffer-multi-engines gts-posframe-pin-render-buffer task))
+
+\f
+;;; [Render] kill-ring render
+
+(defclass gts-kill-ring-render (gts-render) ())
+
+(cl-defmethod gts-out ((_ gts-kill-ring-render) task)
+  (deactivate-mark)
+  (with-slots (err parsed) task
+    (if err
+        (user-error "%s" err)
+      (kill-new (if (listp parsed) (string-join parsed "\n\n") parsed))
+      (message "Translate result already in the kill ring."))))
+
+\f
+;;; [Render] Alert Render
+
+(require 'alert nil t)
+(declare-function alert "ext:alert.el" t t)
+
+(defvar gts-alert-args '(:timeout 10))
+
+(defclass gts-alert-render (gts-render) ())
+
+(cl-defmethod gts-out ((_ gts-alert-render) task)
+  (with-slots (text err parsed) task
+    (if err
+        (user-error "%s" err)
+      (apply #'alert
+             (if (listp parsed) (string-join parsed "\n\n") parsed)
+             :title (if (string-match-p "\n" text) "*Go-Translate*" text)
+             gts-alert-args))))
+
+\f
+;;; [Texter] take current-at-point-or-selection as source text
+
+(defclass gts-current-or-selection-texter (gts-texter) ())
+
+(cl-defmethod gts-text ((_ gts-current-or-selection-texter))
+  (cond ((eq major-mode 'pdf-view-mode)
+         (if (pdf-view-active-region-p)
+             (car (pdf-view-active-region-text))))
+        ((use-region-p)
+         (string-trim (buffer-substring-no-properties (region-beginning) (region-end))))
+        (t (current-word t t))))
+
+;; silence!
+(declare-function pdf-view-active-region-p "ext:pdf-view.el" t t)
+(declare-function pdf-view-active-region-text "ext:pdf-view.el" t t)
+
+\f
+;;; [Texter] take whole-buffer for as source text
+
+(defclass gts-whole-buffer-texter (gts-texter) ())
+
+(cl-defmethod gts-text ((_ gts-whole-buffer-texter))
+  (buffer-substring-no-properties (point-min) (point-max)))
+
+\f
+;;; [Picker] noprompt picker
+
+(defclass gts-noprompt-picker (gts-picker)
+  ((texter :initarg :texter :initform (gts-current-or-selection-texter))))
+
+(defvar gts-picker-current-picker nil)
+(defvar gts-picker-current-path nil)
+
+(cl-defmethod gts-pick ((picker gts-noprompt-picker))
+  (let ((text (gts-text (oref picker texter))))
+    (when (= 0 (length (if text (string-trim text) "")))
+      (user-error "Make sure there is any word at point, or selection exists"))
+    (let ((path (gts-path picker text)))
+      (setq gts-picker-current-path path)
+      (cl-values text path))))
+
+\f
+;;; [Picker] prompt, and flexible
+
+(defclass gts-prompt-picker (gts-picker)
+  ((texter :initarg :texter :initform (gts-current-or-selection-texter))))
+
+(defvar gts-prompt-picker-keymap
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map minibuffer-local-map)
+    (define-key map "\C-g" #'top-level)
+    (define-key map "\C-n" #'gts-prompt-picker-next-path)
+    (define-key map "\C-p" (lambda () (interactive) (gts-prompt-picker-next-path t)))
+    (define-key map "\C-l" #'delete-minibuffer-contents)
+    map)
+  "Minibuffer keymap used when prompt user input.")
+
+(defun gts-prompt-picker-next-path (&optional backwardp)
+  "Pick next available translate path.
+If BACKWARDP is t, then pick the previous one."
+  (interactive "P")
+  (let* ((text (minibuffer-contents))
+         (path (gts-next-path gts-picker-current-picker
+                              text gts-picker-current-path backwardp)))
+    (setq gts-picker-current-path path)
+    (gts-prompt-picker-pick text path)))
+
+(defun gts-prompt-picker-pick (&optional text path)
+  (setq gts-picker-current-path path)
+  (let* ((enable-recursive-minibuffers t)
+         (minibuffer-allow-text-properties t)
+         (prompt (concat (concat "[" (car path) " > " (cdr path) "] ") "Text: "))
+         (text (read-from-minibuffer prompt text gts-prompt-picker-keymap)))
+    (throw 'gts-minibuffer `(,text . ,path))))
+
+(cl-defmethod gts-pick ((picker gts-prompt-picker))
+  (setq gts-picker-current-picker picker)
+  (pcase-let* ((text (gts-text (oref picker texter)))
+               (path (gts-path picker text))
+               (`(,current-text . ,current-path)
+                (catch 'gts-minibuffer (gts-prompt-picker-pick text path))))
+    (when (zerop (length (string-trim current-text)))
+      (user-error "Text should not be null"))
+    (cl-values current-text current-path)))
+
+\f
+;;; [Splitter] split text by paragraph
+
+(defclass gts-paragraph-splitter (gts-splitter) ())
+
+(cl-defmethod gts-split ((_ gts-paragraph-splitter) text)
+  (setq text (split-string text "\n\n" t))
+  (if (and (listp text) (cadr text)) text))
+
+
+(provide 'gts-implements)
+
+;;; gts-implements.el ends here