+++ /dev/null
-;;; 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
+++ /dev/null
-;;; -*- 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")
+++ /dev/null
-;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+(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:
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+(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:
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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