From: Andreas Gerler Date: Sat, 25 Nov 2023 10:35:51 +0000 (+0100) Subject: update go X-Git-Url: http://git.bundesbrandschatzamt.de/?a=commitdiff_plain;h=9855f7ed0571abd0289f2e28e2d06d6a7a9c137f;p=emacsconfig.git update go Signed-off-by: Andreas Gerler --- diff --git a/elpa/go-mode-20191029.2119/go-mode-autoloads.el b/elpa/go-mode-20191029.2119/go-mode-autoloads.el deleted file mode 100644 index 72c1c985..00000000 --- a/elpa/go-mode-20191029.2119/go-mode-autoloads.el +++ /dev/null @@ -1,111 +0,0 @@ -;;; go-mode-autoloads.el --- automatically extracted autoloads -;; -;;; Code: -(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) - -;;;### (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 \\\\[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)) - -;;;*** - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; End: -;;; go-mode-autoloads.el ends here diff --git a/elpa/go-mode-20191029.2119/go-mode-pkg.el b/elpa/go-mode-20191029.2119/go-mode-pkg.el deleted file mode 100644 index 9c6df332..00000000 --- a/elpa/go-mode-20191029.2119/go-mode-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;;; -*- no-byte-compile: t -*- -(define-package "go-mode" "20191029.2119" "Major mode for the Go programming language" 'nil :commit "bd2f6d21196da95447c330cca7d35fb0a6798b4a" :keywords '("languages" "go") :authors '(("The go-mode Authors")) :maintainer '("The go-mode Authors") :url "https://github.com/dominikh/go-mode.el") diff --git a/elpa/go-mode-20191029.2119/go-mode.el b/elpa/go-mode-20191029.2119/go-mode.el deleted file mode 100644 index 5fa76ee5..00000000 --- a/elpa/go-mode-20191029.2119/go-mode.el +++ /dev/null @@ -1,2741 +0,0 @@ -;;; go-mode.el --- Major mode for the Go programming language - -;;; Commentary: - -;; Copyright 2013 The go-mode Authors. All rights reserved. -;; Use of this source code is governed by a BSD-style -;; license that can be found in the LICENSE file. - -;; Author: The go-mode Authors -;; Version: 1.5.0 -;; Package-Version: 20191029.2119 -;; Keywords: languages go -;; URL: https://github.com/dominikh/go-mode.el -;; -;; This file is not part of GNU Emacs. - -;;; Code: - -(require 'cl-lib) -(require 'compile) -(require 'etags) -(require 'ffap) -(require 'find-file) -(require 'ring) -(require 'url) -(require 'xref nil :noerror) ; xref is new in Emacs 25.1 - - -(eval-when-compile - (defmacro go--forward-word (&optional arg) - (if (fboundp 'forward-word-strictly) - `(forward-word-strictly ,arg) - `(forward-word ,arg)))) - -(defun go--delete-whole-line (&optional arg) - "Delete the current line without putting it in the `kill-ring'. -Derived from function `kill-whole-line'. ARG is defined as for that -function." - (setq arg (or arg 1)) - (if (and (> arg 0) - (eobp) - (save-excursion (forward-visible-line 0) (eobp))) - (signal 'end-of-buffer nil)) - (if (and (< arg 0) - (bobp) - (save-excursion (end-of-visible-line) (bobp))) - (signal 'beginning-of-buffer nil)) - (cond ((zerop arg) - (delete-region (progn (forward-visible-line 0) (point)) - (progn (end-of-visible-line) (point)))) - ((< arg 0) - (delete-region (progn (end-of-visible-line) (point)) - (progn (forward-visible-line (1+ arg)) - (unless (bobp) - (backward-char)) - (point)))) - (t - (delete-region (progn (forward-visible-line 0) (point)) - (progn (forward-visible-line arg) (point)))))) - -(defun go-goto-opening-parenthesis (&optional _legacy-unused) - "Move up one level of parentheses. - -Return non-nil if there was a paren to move up to." - ;; The old implementation of go-goto-opening-parenthesis had an - ;; optional argument to speed up the function. It didn't change the - ;; function's outcome. - - ;; Silently fail if there's no matching opening parenthesis. - (let ((open-char (nth 1 (syntax-ppss)))) - (when open-char - (goto-char open-char)))) - - -(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]") -(defconst go--max-dangling-operator-length 2 - "The maximum length of dangling operators. -This must be at least the length of the longest string matched by -‘go-dangling-operators-regexp’ and must be updated whenever that -constant is changed.") - -(defconst go-identifier-regexp "[[:word:][:multibyte:]]+") -(defconst go-type-name-no-prefix-regexp "\\(?:[[:word:][:multibyte:]]+\\.\\)?[[:word:][:multibyte:]]+") -(defconst go-qualified-identifier-regexp (concat go-identifier-regexp "\\." go-identifier-regexp)) -(defconst go-label-regexp go-identifier-regexp) -(defconst go-type-regexp "[[:word:][:multibyte:]*]+") -(defconst go-func-regexp (concat "\\_\\s *\\(" go-identifier-regexp "\\)")) -(defconst go-func-meth-regexp (concat - "\\_\\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 ""))) - (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 "\\_[[:space:]]+\\(" go-identifier-regexp "\\)[[:space:]]*\\(?:=[[:space:]]*\\)?\\(?:" go-type-name-regexp "\\)?" ) (1 font-lock-type-face) (2 font-lock-type-face nil t)) - - ;; Arrays/slices: [] | [123] | [some.Const] | [someConst] | [...] - (,(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 "\\_\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) - - ;; Map key type - (,(concat "\\_\\[" go-type-name-regexp) 1 font-lock-type-face) - - ;; Channel type - (,(concat "\\_[[: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 "\\_ (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 \\\\[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) "")) - (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 "\\")) - (go-goto-function arg))) - - ;; If we're already looking at an anonymous func, look for the - ;; surrounding function. - ((and (looking-at "\\") - (not (looking-at "^func\\>"))) - (re-search-backward "\\" nil t)) - - ((not (looking-at "\\")) - ;; 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 "\\" 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 "\\")) - (re-search-forward "\\" 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 "\\")) - (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 "\\")) - (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 "\\ b" st) - st) - "Syntax table for `go-dot-mod-mode'.") - -(defconst go-dot-mod-mode-keywords - '("module" "go" "require" "replace" "exclude") - "All keywords for go.mod files. Used for font locking.") - -(defvar go-dot-mod-font-lock-keywords - `( - (,(concat "^\\s-*" (regexp-opt go-dot-mod-mode-keywords t) "\\s-") . font-lock-keyword-face)) - "Keyword highlighting specification for `go-dot-mod-mode'.") - -;;;###autoload -(define-derived-mode go-dot-mod-mode fundamental-mode "Go Mod" - "A major mode for editing go.mod files." - :syntax-table go-dot-mod-mode-syntax-table - (set (make-local-variable 'comment-start) "// ") - (set (make-local-variable 'comment-end) "") - (set (make-local-variable 'comment-use-syntax) t) - (set (make-local-variable 'comment-start-skip) "\\(//+\\)\\s *") - - (set (make-local-variable 'font-lock-defaults) - '(go-dot-mod-font-lock-keywords)) - (set (make-local-variable 'indent-line-function) 'go-mode-indent-line) - - ;; Go style - (setq indent-tabs-mode t) - - ;; we borrow the go-mode-indent function so we need this buffer cache - (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql)) - (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t)) - -;;;###autoload -(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-dot-mod-mode)) - -;; Polyfills for functions added in Emacs 26. Remove these once we don’t -;; support Emacs 25 any more. -(defalias 'go--file-local-name - (if (fboundp 'file-local-name) #'file-local-name - (lambda (file) (or (file-remote-p file 'localname) file)))) - -(defalias 'go--make-nearby-temp-file - (if (fboundp 'make-nearby-temp-file) #'make-nearby-temp-file - (lambda (prefix &optional dir-flag suffix) - (let ((temporary-file-directory (go--temporary-file-directory))) - (make-temp-file prefix dir-flag suffix))))) - -(defalias 'go--temporary-file-directory - (if (fboundp 'temporary-file-directory) #'temporary-file-directory - (lambda () - (let ((remote (file-remote-p default-directory))) - (if remote - ;; Assume that /tmp is a temporary directory on the remote host. - ;; This won’t work on Windows. - (concat remote "/tmp") - temporary-file-directory))))) - -(provide 'go-mode) - -;;; go-mode.el ends here diff --git a/elpa/go-mode-20230624.2315/go-mode-autoloads.el b/elpa/go-mode-20230624.2315/go-mode-autoloads.el new file mode 100644 index 00000000..3c049d97 --- /dev/null +++ b/elpa/go-mode-20230624.2315/go-mode-autoloads.el @@ -0,0 +1,122 @@ +;;; go-mode-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (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 \\\\[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")) + +;;;*** + +;;;### (autoloads nil nil ("go-mode-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; go-mode-autoloads.el ends here diff --git a/elpa/go-mode-20230624.2315/go-mode-pkg.el b/elpa/go-mode-20230624.2315/go-mode-pkg.el new file mode 100644 index 00000000..73f169e0 --- /dev/null +++ b/elpa/go-mode-20230624.2315/go-mode-pkg.el @@ -0,0 +1,14 @@ +(define-package "go-mode" "20230624.2315" "Major mode for the Go programming language" + '((emacs "26.1")) + :commit "f21347ae9cf68ea33031bf5125896e8f16c8183c" :authors + '(("The go-mode Authors")) + :maintainers + '(("The go-mode Authors")) + :maintainer + '("The go-mode Authors") + :keywords + '("languages" "go") + :url "https://github.com/dominikh/go-mode.el") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/go-mode-20230624.2315/go-mode.el b/elpa/go-mode-20230624.2315/go-mode.el new file mode 100644 index 00000000..d1152eb0 --- /dev/null +++ b/elpa/go-mode-20230624.2315/go-mode.el @@ -0,0 +1,3056 @@ +;;; go-mode.el --- Major mode for the Go programming language + +;;; Commentary: + +;; Copyright 2013 The go-mode Authors. All rights reserved. +;; Use of this source code is governed by a BSD-style +;; license that can be found in the LICENSE file. + +;; Author: The go-mode Authors +;; Version: 1.6.0 +;; Keywords: languages go +;; Package-Requires: ((emacs "26.1")) +;; URL: https://github.com/dominikh/go-mode.el +;; +;; This file is not part of GNU Emacs. + +;;; Code: + +(require 'cl-lib) +(require 'compile) +(require 'etags) +(require 'ffap) +(require 'find-file) +(require 'ring) +(require 'url) +(require 'xref) + + +(eval-when-compile + (defmacro go--forward-word (&optional arg) + (if (fboundp 'forward-word-strictly) + `(forward-word-strictly ,arg) + `(forward-word ,arg)))) + +(defun go--delete-whole-line (&optional arg) + "Delete the current line without putting it in the `kill-ring'. +Derived from function `kill-whole-line'. ARG is defined as for that +function." + (setq arg (or arg 1)) + (if (and (> arg 0) + (eobp) + (save-excursion (forward-visible-line 0) (eobp))) + (signal 'end-of-buffer nil)) + (if (and (< arg 0) + (bobp) + (save-excursion (end-of-visible-line) (bobp))) + (signal 'beginning-of-buffer nil)) + (cond ((zerop arg) + (delete-region (progn (forward-visible-line 0) (point)) + (progn (end-of-visible-line) (point)))) + ((< arg 0) + (delete-region (progn (end-of-visible-line) (point)) + (progn (forward-visible-line (1+ arg)) + (unless (bobp) + (backward-char)) + (point)))) + (t + (delete-region (progn (forward-visible-line 0) (point)) + (progn (forward-visible-line arg) (point)))))) + +(defun go-goto-opening-parenthesis (&optional _legacy-unused) + "Move up one level of parentheses. + +Return non-nil if there was a paren to move up to." + ;; The old implementation of go-goto-opening-parenthesis had an + ;; optional argument to speed up the function. It didn't change the + ;; function's outcome. + + ;; Silently fail if there's no matching opening parenthesis. + (let ((open-char (nth 1 (syntax-ppss)))) + (when open-char + (goto-char open-char)))) + + +(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]") +(defconst go--max-dangling-operator-length 2 + "The maximum length of dangling operators. +This must be at least the length of the longest string matched by +‘go-dangling-operators-regexp’ and must be updated whenever that +constant is changed.") + +(defconst go-identifier-regexp "[[:word:][:multibyte:]]+") +(defconst go-type-name-no-prefix-regexp "\\(?:[[:word:][:multibyte:]]+\\.\\)?[[:word:][:multibyte:]]+") +(defconst go-qualified-identifier-regexp (concat go-identifier-regexp "\\." go-identifier-regexp)) +(defconst go-label-regexp go-identifier-regexp) +(defconst go-type-regexp "[[:word:][:multibyte:]*]+") +(defconst go-func-regexp (concat "\\_\\s *\\(" go-identifier-regexp "\\)")) +(defconst go-func-meth-regexp (concat + "\\_\\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 ""))) + (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: [] | [123] | [some.Const] | [someConst] | [...] + (,(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 "\\_\\[" go-type-name-regexp) 1 font-lock-type-face) + + ;; Channel type + (,(concat "\\_[[: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 "\\_" 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 "\\_ (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 "\\_\\[\\(?:\\[[^]]*\\]\\)*[^]]*\\]" 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 \\\\[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) "")) + (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 "\\")) + (go-goto-function arg))) + + ;; If we're already looking at an anonymous func, look for the + ;; surrounding function. + ((and (looking-at "\\") + (not (looking-at "^func\\>"))) + (re-search-backward "\\" nil t)) + + ((not (looking-at "\\")) + ;; 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 "\\" 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 "\\")) + (re-search-forward "\\" 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 "\\")) + (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 "\\")) + (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 "\\ b" st) + st) + "Syntax table for `go-dot-mod-mode'.") + +(defconst go-dot-mod-mode-keywords + '("module" "go" "require" "replace" "exclude") + "All keywords for go.mod files. Used for font locking.") + +(defgroup go-dot-mod nil + "Options specific to `go-dot-mod-mode`." + :group 'go) + +(defface go-dot-mod-module-name '((t :inherit default)) + "Face for module name in \"require\" list." + :group 'go-dot-mod) + +(defface go-dot-mod-module-version '((t :inherit default)) + "Face for module version in \"require\" list." + :group 'go-dot-mod) + +(defface go-dot-mod-module-semver '((t :inherit go-dot-mod-module-version)) + "Face for module semver in \"require\" list." + :group 'go-dot-mod) + + +(defvar go-dot-mod-font-lock-keywords + `( + (,(concat "^\\s-*\\(" (regexp-opt go-dot-mod-mode-keywords t) "\\)\\s-") 1 font-lock-keyword-face) + ("\\(?:^\\|=>\\)\\s-*\\([^[:space:]\n()]+\\)\\(?:\\s-+\\(v[0-9]+\\.[0-9]+\\.[0-9]+\\)\\([^[:space:]\n]*\\)\\)?" (1 'go-dot-mod-module-name) (2 'go-dot-mod-module-semver nil t) (3 'go-dot-mod-module-version nil t))) + "Keyword highlighting specification for `go-dot-mod-mode'.") + +;;;###autoload +(define-derived-mode go-dot-mod-mode fundamental-mode "Go Mod" + "A major mode for editing go.mod files." + :syntax-table go-dot-mod-mode-syntax-table + (set (make-local-variable 'comment-start) "// ") + (set (make-local-variable 'comment-end) "") + (set (make-local-variable 'comment-use-syntax) t) + (set (make-local-variable 'comment-start-skip) "\\(//+\\)\\s *") + + (set (make-local-variable 'font-lock-defaults) + '(go-dot-mod-font-lock-keywords)) + (set (make-local-variable 'indent-line-function) 'go-mode-indent-line) + + ;; Go style + (setq indent-tabs-mode t) + + ;; we borrow the go-mode-indent function so we need this buffer cache + (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql)) + (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-dot-mod-mode)) + +(defconst go-dot-work-mode-keywords + '("go" "replace" "use") + "All keywords for go.work files. Used for font locking.") + +;;;###autoload +(define-derived-mode go-dot-work-mode fundamental-mode "Go Work" + "A major mode for editor go.work files." + :syntax-table go-dot-mod-mode-syntax-table + (set (make-local-variable 'comment-start) "// ") + (set (make-local-variable 'comment-end) "") + (set (make-local-variable 'comment-use-syntax) t) + (set (make-local-variable 'comment-start-skip) "\\(//+\\)\\s *") + + (set (make-local-variable 'font-lock-defaults) + '(go-dot-work-mode-keywords)) + (set (make-local-variable 'indent-line-function) 'go-mode-indent-line) + + ;; Go style + (setq indent-tabs-mode t) + + ;; we borrow the go-mode-indent function so we need this buffer cache + (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql)) + (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("go\\.work\\'" . go-dot-work-mode)) + +;; The following functions were copied (and modified) from rust-mode.el. +;; +;; Copyright (c) 2015 The Rust Project Developers +;; +;; Permission is hereby granted, free of charge, to any +;; person obtaining a copy of this software and associated +;; documentation files (the "Software"), to deal in the +;; Software without restriction, including without +;; limitation the rights to use, copy, modify, merge, +;; publish, distribute, sublicense, and/or sell copies of +;; the Software, and to permit persons to whom the Software +;; is furnished to do so, subject to the following +;; conditions: +;; +;; The above copyright notice and this permission notice +;; shall be included in all copies or substantial portions +;; of the Software. + +(defun go--fill-prefix-for-comment-start (line-start) + "Determine what to use for `fill-prefix' based on the text at LINE-START." + (let ((result + ;; Replace /* with same number of spaces + (replace-regexp-in-string + "\\(?:/\\*+?\\)[!*]?" + (lambda (s) + (let ((offset (if (eq t + (compare-strings "/*" nil nil + s + (- (length s) 2) + (length s))) + 1 2))) + (make-string (1+ (- (length s) offset)) ?\x20))) + line-start))) + ;; Make sure we've got at least one space at the end + (if (not (= (aref result (- (length result) 1)) ?\x20)) + (setq result (concat result " "))) + result)) + +(defun go--in-comment-paragraph (body) + ;; We might move the point to fill the next comment, but we don't want it + ;; seeming to jump around on the user + (save-excursion + ;; If we're outside of a comment, with only whitespace and then a comment + ;; in front, jump to the comment and prepare to fill it. + (when (not (go-in-comment-p)) + (beginning-of-line) + (when (looking-at (concat "[[:space:]\n]*" comment-start-skip)) + (goto-char (match-end 0)))) + + ;; If we're at the beginning of a comment paragraph with nothing but + ;; whitespace til the next line, jump to the next line so that we use the + ;; existing prefix to figure out what the new prefix should be, rather than + ;; inferring it from the comment start. + (while (save-excursion + (end-of-line) + (and (go-in-comment-p) + (save-excursion + (beginning-of-line) + (looking-at paragraph-start)) + (looking-at "[[:space:]]*$") + (nth 4 (syntax-ppss (line-beginning-position 2))))) + (goto-char (line-beginning-position 2))) + + ;; If we're on the last line of a multiline-style comment that started + ;; above, back up one line so we don't mistake the * of the */ that ends + ;; the comment for a prefix. + (when (save-excursion + (and (nth 4 (syntax-ppss (line-beginning-position 1))) + (looking-at "[[:space:]]*\\*/"))) + (goto-char (line-end-position 0))) + (funcall body))) + +(defun go--with-comment-fill-prefix (body) + (let* + ((line-string (buffer-substring-no-properties + (line-beginning-position) (line-end-position))) + (line-comment-start + (when (go-in-comment-p) + (cond + ;; If we're inside the comment and see a * prefix, use it + ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)" + line-string) + (match-string 1 line-string)) + ;; If we're at the start of a comment, figure out what prefix + ;; to use for the subsequent lines after it + ((string-match (concat "[[:space:]]*" comment-start-skip) line-string) + (go--fill-prefix-for-comment-start + (match-string 0 line-string)))))) + (fill-prefix + (or line-comment-start + fill-prefix))) + (funcall body))) + +(defun go--find-fill-prefix () + (go--in-comment-paragraph + (lambda () + (go--with-comment-fill-prefix + (lambda () + fill-prefix))))) + +(defun go-fill-paragraph (&rest args) + "Special wrapping for `fill-paragraph'. +This handles multi-line comments with a * prefix on each line." + (go--in-comment-paragraph + (lambda () + (go--with-comment-fill-prefix + (lambda () + (let + ((fill-paragraph-function + (if (not (eq fill-paragraph-function 'go-fill-paragraph)) + fill-paragraph-function)) + (fill-paragraph-handle-comment t)) + (apply 'fill-paragraph args) + t)))))) + +(defun go--do-auto-fill (&rest args) + "Special wrapping for `do-auto-fill'. +This handles multi-line comments with a * prefix on each line." + (go--with-comment-fill-prefix + (lambda () + (apply 'do-auto-fill args) + t))) + +(defun go--fill-forward-paragraph (arg) + ;; This is to work around some funny behavior when a paragraph separator is + ;; at the very top of the file and there is a fill prefix. + (let ((fill-prefix nil)) (forward-paragraph arg))) + +(defun go--comment-indent-new-line (&optional arg) + (go--with-comment-fill-prefix + (lambda () (comment-indent-new-line arg)))) + + + +(provide 'go-mode) + +;;; go-mode.el ends here diff --git a/elpa/go-translate-20230503.303/go-translate-autoloads.el b/elpa/go-translate-20230503.303/go-translate-autoloads.el new file mode 100644 index 00000000..358b41e7 --- /dev/null +++ b/elpa/go-translate-20230503.303/go-translate-autoloads.el @@ -0,0 +1,99 @@ +;;; go-translate-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (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")) + +;;;*** + +;;;### (autoloads nil "gts-core" "gts-core.el" (0 0 0 0)) +;;; Generated autoloads from gts-core.el + +(register-definition-prefixes "gts-core" '("gts-")) + +;;;*** + +;;;### (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-")) + +;;;*** + +;;;### (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-")) + +;;;*** + +;;;### (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-")) + +;;;*** + +;;;### (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-")) + +;;;*** + +;;;### (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-")) + +;;;*** + +;;;### (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")) + +;;;*** + +;;;### (autoloads nil "gts-faces" "gts-faces.el" (0 0 0 0)) +;;; Generated autoloads from gts-faces.el + +(register-definition-prefixes "gts-faces" '("gts-p")) + +;;;*** + +;;;### (autoloads nil "gts-implements" "gts-implements.el" (0 0 0 +;;;;;; 0)) +;;; Generated autoloads from gts-implements.el + +(register-definition-prefixes "gts-implements" '("gts-")) + +;;;*** + +;;;### (autoloads nil nil ("go-translate-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; go-translate-autoloads.el ends here diff --git a/elpa/go-translate-20230503.303/go-translate-pkg.el b/elpa/go-translate-20230503.303/go-translate-pkg.el new file mode 100644 index 00000000..d7d1cecd --- /dev/null +++ b/elpa/go-translate-20230503.303/go-translate-pkg.el @@ -0,0 +1,14 @@ +(define-package "go-translate" "20230503.303" "Translation framework supports multiple engines such as Google/Bing/DeepL" + '((emacs "27.1")) + :commit "59506f69503cca65291fa5ccd0a326e716338c23" :authors + '(("lorniu" . "lorniu@gmail.com")) + :maintainers + '(("lorniu" . "lorniu@gmail.com")) + :maintainer + '("lorniu" . "lorniu@gmail.com") + :keywords + '("convenience") + :url "https://github.com/lorniu/go-translate") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/go-translate-20230503.303/go-translate.el b/elpa/go-translate-20230503.303/go-translate.el new file mode 100644 index 00000000..100ef599 --- /dev/null +++ b/elpa/go-translate-20230503.303/go-translate.el @@ -0,0 +1,90 @@ +;;; go-translate.el --- Translation framework supports multiple engines such as Google/Bing/DeepL -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu + +;; Author: lorniu +;; 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) + + +;;; Commands + +(defvar gts-default-translator + (gts-translator :picker + (gts-prompt-picker) + ;;(gts-noprompt-picker) + ;;(gts-noprompt-picker :texter (gts-whole-buffer-texter)) + + :engines + (list + (gts-bing-engine) + (gts-google-engine :parser (gts-google-summary-parser)) + (gts-google-rpc-engine) + + ;;(gts-google-engine) + ;;(gts-google-rpc-engine) + ;;(gts-deepl-engine :auth-key "2e20bade-88e9-02f3-169f-ab3c445d7984:fx" :pro nil) + + ;;(gts-google-engine :parser (gts-google-summary-parser)) + ;;(gts-google-engine :parser (gts-google-parser)) + ;;(gts-google-rpc-engine :parser (gts-google-rpc-summary-parser)) + ) + + :render + (gts-buffer-render) + + ;;(gts-posframe-pop-render) + ;;(gts-posframe-pop-render :backcolor "#333333" :forecolor "#ffffff") + + ;;(gts-posframe-pin-render) + ;;(gts-posframe-pin-render :position (cons 1200 20)) + ;;(gts-posframe-pin-render :width 80 :height 25 :position (cons 1000 20) :forecolor "#ffffff" :backcolor "#111111") + + ;;(gts-kill-ring-render) + + :splitter nil + ;; (gts-paragraph-splitter) + )) + +;;;###autoload +(defun gts-do-translate () + "Do the translate" + (interactive) + (gts-translate gts-default-translator)) + + +(provide 'go-translate) + +;;; go-translate.el ends here diff --git a/elpa/go-translate-20230503.303/gts-core.el b/elpa/go-translate-20230503.303/gts-core.el new file mode 100644 index 00000000..998c3dfc --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-core.el @@ -0,0 +1,673 @@ +;;; gts-core.el --- Translate engines and utils -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; 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) + + +;;; 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)) + + +;;; 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) + + +;;; 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")))) + + +;;; 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)) + + +;;; 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"))) + + +;;; 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)))))) + + +;;; Splitter + +(cl-defgeneric gts-split (gts-splitter text) + "Split TEXT to several parts. +Return list of string, or nil if split failed.") + + +;;; 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)))) + + +;;; 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))))))) + + +;;; 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)))))))) + + +;;; Translator + +(defvar gts-text-delimiter "34587612321123") + +(defvar gts-current-command nil "The command invoked by `gts-translate'.") +(defvar gts-current-picker nil "Picker used in current translate.") +(defvar gts-current-engines nil "Engines used in current translate.") +(defvar gts-current-render nil "Render used in current translate.") + +(cl-defmethod initialize-instance :after ((this gts-translator) &rest _) + (unless (gts-ensure-plain (oref this render)) + (oset this render (gts-render))) + (unless (gts-ensure-plain (oref this picker)) + (oset this picker (gts-noprompt-picker)))) + +(cl-defmethod gts-get ((this gts-translator) &rest slots) + "Return the normalized value of SLOTS in THIS translator." + (cl-loop for slot in slots + for value = + (cl-flet ((ensure (v) (or v (user-error "No %s found in current translator" slot)))) + (pcase slot + ('splitter (gts-ensure-plain (slot-value this slot))) + ('picker (ensure (gts-ensure-plain (or gts-current-picker (slot-value this slot))))) + ('engines (ensure (gts-ensure-list (gts-ensure-plain (or gts-current-engines (slot-value this slot)))))) + ('render (ensure (gts-ensure-plain (or gts-current-render (slot-value this slot))))) + (_ (slot-value this slot)))) + collect value into values + finally (return (if (cadr slots) (apply #'cl-values values) (car values))))) + +(cl-defmethod gts-init ((this gts-translator) text path) + "Init all status and tasks in THIS translator." + ;; input + (unless (and text path) + (let ((input (gts-pick (gts-get this 'picker)))) + (setq text (cl-nth-value 0 input)) + (setq path (cl-nth-value 1 input)))) + (oset this text text) + (oset this path path) + (setq gts-picker-last-path path) + (gts-do-log 'translator (format "\nBegin to translate %s" path)) + (gts-do-log 'text text) + (cl-multiple-value-bind (splitter engines render) (gts-get this 'splitter 'engines 'render 'version) + ;; reset + (oset this state 0) + (oset this task-queue nil) + (oset this plan-cnt (length engines)) + (oset this version (time-to-seconds)) + ;; tasks + (let ((splitted (if splitter (gts-split splitter text)))) + (oset this sptext splitted) + (cl-loop for engine in engines + for task = (gts-task :translator this + :engine engine + :render render + :from (car path) :to (cdr path) + :text (if splitted (string-join splitted (concat "\n" gts-text-delimiter "\n")) text)) + do (gts-add-task this task)) + (gts-do-log 'text (format "splitted? %s!" (if splitted "Yes" "No")))))) + +(cl-defmethod gts-add-task ((this gts-translator) task) + "Add a new TASK to THIS translator." + (with-slots (id engine render) task + (with-slots (text path plan-cnt task-queue state) this + (when (= state 0) + (setf task-queue (append task-queue (list task))) + (gts-do-log 'translator + (format "- add task %s: (%s/%s)" id + (eieio-object-class-name engine) (eieio-object-class-name render)))) + (when (and (= state 0) (= (length task-queue) plan-cnt)) + (setf state 1))))) + +(cl-defmethod gts-done-tasks ((this gts-translator)) + "Return the finished tasks in THIS translator." + (with-slots (task-queue) this + (cl-remove-if-not + (lambda (task) (or (oref task err) (oref task raw))) task-queue))) + +(defun gts-update-raw (task resp &optional error) + "Update non-parsed RESP result to TASK. +If ERROR is not nil, then update task with failure." + (declare (indent 1)) + (with-slots (id raw err translator) task + (with-slots (task-queue plan-cnt state) translator + (setf raw resp) + (setf err error) + (when (and (= state 2) (= plan-cnt (length (gts-done-tasks translator)))) + (setf state 3)) + (gts-do-log 'translator (format "%s: **responsed** %s" id (or error "success!")))))) + +(defun gts-update-parsed (task result &optional metadata filter) + "Update TASK with parsed RESULT, update METADATA if exists. +FILTER the parsed result if necessary, it's a function take parsed result +as argument." + (declare (indent 1)) + (with-slots (id raw parsed err meta translator) task + (if raw + (condition-case er + (let ((r (if (or (listp result) (not (gts-get translator 'sptext))) + result + (gts-do-log 'translator + (format "%s: result contains delimiter, so split to list" id)) + (split-string result gts-text-delimiter nil "[ \t\n\r]+")))) + (setf parsed (if filter (funcall filter r) r)) + (if metadata (setf meta metadata))) + (error (setf err er))) + (gts-update-raw task t "Mybe Null Response")))) + +(defun gts-render-fail (task error) + "Directly render the ERROR message and finish the TASK." + (declare (indent 1)) + (gts-do-log 'translator (format "error? %s" error)) + (if task + (with-slots (translator render) task + (let ((engines (gts-get translator 'engines))) + (gts-update-raw task nil error) + (if (cdr engines) + (gts-me-out render task) + (gts-out render task)))) + (signal 'error error))) + +(cl-defmethod gts-translate ((this gts-translator) &optional text path) + "Fire a translation for THIS translator instance. +When TEXT and PATH is nil then pick them via `gts-pick'." + ;; dynamic + (setq gts-current-command this-command) + (setq gts-current-picker (oref this picker)) + (setq gts-current-engines (oref this engines)) + (setq gts-current-render (oref this render)) + ;; initialize + (gts-init this text path) + (cl-multiple-value-bind (engines render tasks text path) + (gts-get this 'engines 'render 'task-queue 'text 'path) + (let ((buf (current-buffer))) + ;; single engine + (if (not (cdr engines)) + (let ((task (car tasks))) + (gts-pre render this) + (gts-translate (car engines) task + (lambda () + (with-current-buffer buf + (gts-out render task))))) + ;; multiple engines + (gts-me-pre render this) + (dolist (task tasks) + (condition-case er + (gts-translate (oref task engine) task + (lambda () + (with-current-buffer buf + (gts-me-out render task)))) + (error + (gts-render-fail task er)))) + (gts-me-out render (gts-task :text text :from (car path) :to (cdr path) :translator this)))))) + + +(provide 'gts-core) + +;;; gts-core.el ends here diff --git a/elpa/go-translate-20230503.303/gts-engine-bing.el b/elpa/go-translate-20230503.303/gts-engine-bing.el new file mode 100644 index 00000000..202d34bd --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-engine-bing.el @@ -0,0 +1,155 @@ +;;; gts-engine-bing.el --- Microsoft Translate -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; 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 "%s") + + (parser :initform (gts-bing-parser)))) + + +;;; 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))))) + + +;;; 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")))) + + +;;; Parser + +(cl-defmethod gts-parse ((_ gts-bing-parser) task) + (with-temp-buffer + (set-buffer-multibyte t) + (insert (oref task raw)) + (decode-coding-region (point-min) (point-max) 'utf-8) + (gts-do-log 'bing (string-trim (buffer-string))) + (goto-char (point-min)) + (if-let* ((json (json-read)) + (result (ignore-errors (cdr (assoc 'text (aref (cdr (assoc 'translations (aref json 0))) 0)))))) + (gts-update-parsed task result) + (setq gts-bing-token-maybe-invalid t) ; refresh token when error occurred + (gts-update-raw task nil (buffer-string))))) + + +(provide 'gts-engine-bing) + +;;; gts-engine-bing.el ends here diff --git a/elpa/go-translate-20230503.303/gts-engine-deepl.el b/elpa/go-translate-20230503.303/gts-engine-deepl.el new file mode 100644 index 00000000..9fa0a39d --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-engine-deepl.el @@ -0,0 +1,127 @@ +;;; gts-engine-deepl.el --- Translate Engine for DeepL -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; SPDX-License-Identifier: MIT + +;;; Commentary: + +;; https://www.deepl.com + +;;; Code: + +(require 'gts-implements) + + +;;; 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"))) + + +;;; 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)) + + +;;; 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))))))) + + +;;; Parser + +(cl-defmethod gts-parse ((_ gts-deepl-parser) task) + (let* ((json (json-read-from-string (oref task raw))) + (str (mapconcat (lambda (r) (cdr (cadr r))) (cdar json) "\n")) + (filter (lambda (parsed) + (cl-loop for p in (gts-ensure-list parsed) + collect (gts-deepl-fill-output p) into ps + finally (return (if (cdr ps) ps (car ps))))))) + (gts-update-parsed task (decode-coding-string str 'utf-8) nil filter))) + + +(provide 'gts-engine-deepl) + +;;; gts-engine-deepl.el ends here diff --git a/elpa/go-translate-20230503.303/gts-engine-google-rpc.el b/elpa/go-translate-20230503.303/gts-engine-google-rpc.el new file mode 100644 index 00000000..8c784c48 --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-engine-google-rpc.el @@ -0,0 +1,207 @@ +;;; gts-engine-google-rpc.el --- Google translation with rpc api -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; 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) + + +;;; 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"))) + + +;;; 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")))) + + +;;; 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 "\\|\\|\\|" "" (gts-aref-for json 0 1 0 0 1)))) + + +(provide 'gts-engine-google-rpc) + +;;; gts-engine-google-rpc.el ends here diff --git a/elpa/go-translate-20230503.303/gts-engine-google.el b/elpa/go-translate-20230503.303/gts-engine-google.el new file mode 100644 index 00000000..2ac39ba5 --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-engine-google.el @@ -0,0 +1,424 @@ +;;; gts-engine-google.el --- Google translate module -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; SPDX-License-Identifier: MIT + +;;; Commentary: + +;; http://translate.google.com + +;;; Code: + +(require 'gts-implements) + + +;;; 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)))) + + +;;; 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))))) + + +;;; 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)))) + + +;;; Token Key algorithm (deprecated) + +;; This algorithm is from `google-translate' project. +;; https://github.com/atykhonov/google-translate/blob/master/google-translate-tk.el + +(defvar gts-google-token--bit-v-len 32) + +(defun gts-google-token--bit-v-2comp (v) + "Return the two's complement of V." + (let* ((vc (vconcat v)) + (len (length vc))) + ;; Complement of v + (cl-loop for i from 0 below len do + (aset vc i (logxor (aref vc i) 1))) + ;; vc = complement of v + 1 + (cl-loop for i downfrom (1- len) to 0 + do (aset vc i (logxor (aref vc i) 1)) + when (> (aref vc i) 0) return nil) + vc)) + +(defun gts-google-token--number-to-bit-v (n) + "Return a bit vector from N." + (if (< n 0) (gts-google-token--bit-v-2comp + (gts-google-token--number-to-bit-v (abs n))) + (let ((v (make-vector gts-google-token--bit-v-len 0))) + (cl-loop for i downfrom (1- gts-google-token--bit-v-len) to 0 + with q + when (< n 1) return nil do + (setq q (ffloor (* n 0.5))) + (aset v i (floor (- n (* 2.0 q)))) + (setq n q)) + v))) + +(defun gts-google-token--bit-v-to-number (v) + "Return a floating-point number from V." + (if (and (> (aref v 0) 0) + ;; Exclude [1 0 ... 0] + (cl-loop for i from 1 below gts-google-token--bit-v-len + thereis (> (aref v i) 0))) + (- (gts-google-token--bit-v-to-number (gts-google-token--bit-v-2comp v))) + (funcall (if (> (aref v 0) 0) #'- #'+) + (cl-reduce (lambda (acc e) (+ (* acc 2.0) e)) + v :initial-value 0.0)))) + +(defun gts-google-token--logfn (fn n1 n2) + "Helper function for logical FN with N1 and N2." + (let ((v1 (gts-google-token--number-to-bit-v n1)) + (v2 (gts-google-token--number-to-bit-v n2)) + (v (make-vector gts-google-token--bit-v-len 0))) + (cl-loop for i from 0 below gts-google-token--bit-v-len do + (aset v i (funcall fn (aref v1 i) (aref v2 i)))) + (gts-google-token--bit-v-to-number v))) + +(defun gts-google-token--logand (n1 n2) + "Return a floating-point number from N1 and N2." + (gts-google-token--logfn #'logand n1 n2)) + +(defun gts-google-token--logxor (n1 n2) + "Return a floating-point number from N1 and N2." + (gts-google-token--logfn #'logxor n1 n2)) + +(defun gts-google-token--lsh (n d) + "Return a floating-point number. +Shift the bits in N to the left or rihgt D places. +D is an integer." + (let ((v (gts-google-token--number-to-bit-v n)) + (v-result (make-vector gts-google-token--bit-v-len 0))) + (if (< d 0) + ;; Shift Right Logical + (cl-loop for i from (abs d) below gts-google-token--bit-v-len + for j from 0 do + (aset v-result i (aref v j))) + ;; Shift Left Logical + (cl-loop for i from d below gts-google-token--bit-v-len + for j from 0 do + (aset v-result j (aref v i)))) + (gts-google-token--bit-v-to-number v-result))) + +(defun gts-google-token--gen-rl (a b) + "Gen rl from A and B." + (cl-loop for c from 0 below (- (length b) 2) by 3 + for d = (aref b (+ c 2)) do + (setq d (if (>= d ?a) (- d 87) (- d ?0))) + (setq d (if (= (aref b (1+ c)) ?+) + (gts-google-token--lsh a (- d)) + (gts-google-token--lsh a d))) + (setq a (if (= (aref b c) ?+) + (gts-google-token--logand (+ a d) 4294967295.0) + (gts-google-token--logxor a d)))) + a) + +(defun gts-google-tkk (token text) + "Calculate the Token for search TEXT. + +It will use the tkk from Google translate page." + (let* ((b (car token)) + (d1 (cdr token)) + (ub "+-3^+b+-f") + (vb "+-a^+6") + (a (cl-loop for c across (encode-coding-string text 'utf-8) + for rr = (gts-google-token--gen-rl (+ (or rr b) c) vb) + finally (return rr)))) + (setq a (gts-google-token--gen-rl a ub)) + (setq a (gts-google-token--logxor a d1)) + (if (< a 0) (setq a (+ (gts-google-token--logand a 2147483647.0) 2147483648.0))) + (setq a (ffloor (mod a 1e6))) + (format "%s.%s" + (car (split-string (number-to-string a) "\\.")) + (car (split-string (number-to-string + (gts-google-token--logxor a b)) + "\\."))))) + + +(provide 'gts-engine-google) + +;;; gts-engine-google.el ends here diff --git a/elpa/go-translate-20230503.303/gts-engine-stardict.el b/elpa/go-translate-20230503.303/gts-engine-stardict.el new file mode 100644 index 00000000..3ce059ac --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-engine-stardict.el @@ -0,0 +1,64 @@ +;;; gts-engine-stardict.el --- StarDict -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; SPDX-License-Identifier: MIT + +;;; Commentary: + +;; Engine for sdcv (StarDict Console Version). +;; +;; sdcv is an offline translate software. It can be used when no network. +;; +;; Make sure sdcv is installed on your system: +;; +;; sudo pacman -S sdcv +;; +;; Then download dictionary data to ~/.stardict/dic or /usr/share/stardict/dic: +;; +;; # For example from http://download.huzheng.org: +;; +;; mkdir -p ~/.stardict/dic +;; cd ~/.stardict/dic +;; wget http://download.huzheng.org/zh_CN/stardict-langdao-ce-gb-2.4.2.tar.bz2 +;; wget http://download.huzheng.org/zh_CN/stardict-langdao-ce-gb-2.4.2.tar.bz2 +;; tar xvf stardict-langdao-ec-gb-2.4.2.tar.bz2 +;; tar xvf stardict-langdao-ce-gb-2.4.2.tar.bz2 +;; sdcv -l +;; +;; TODO: Provide more options and a better parser. + +;;; Code: + +(require 'gts-implements) + +(defclass gts-stardict-engine (gts-engine) + ((tag :initform "StarDict") + (parser :initform (gts-parser)))) + +(defcustom gts-stardict-program "sdcv" + "Executable command of sdcv (StarDict Console Version)." + :type 'string + :group 'go-translate) + +(defcustom gts-stardict-args '("-n" "-c" "-0" "-1") + "Arguments passed to command `sdcv'." + :type 'list + :group 'go-translate) + +(cl-defmethod gts-translate ((engine gts-stardict-engine) task rendercb) + (let* ((text (oref task text)) + (parser (oref engine parser)) + (result (with-temp-buffer + (require 'ansi-color) + (apply #'call-process gts-stardict-program nil t nil + (append gts-stardict-args (list text))) ;--json-output + (if (string-equal (buffer-substring 1 16) "Nothing similar") + "No translation result found, sorry :(" + (ansi-color-apply (buffer-string)))))) + (gts-update-raw task result) + (gts-parse parser task) + (funcall rendercb))) + +(provide 'gts-engine-stardict) + +;;; gts-engine-stardict.el ends here diff --git a/elpa/go-translate-20230503.303/gts-engine-youdao.el b/elpa/go-translate-20230503.303/gts-engine-youdao.el new file mode 100644 index 00000000..594d3452 --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-engine-youdao.el @@ -0,0 +1,58 @@ +;;; gts-engine-youdao.el --- YouDao Dict -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; SPDX-License-Identifier: MIT + +;;; Commentary: + +;; 有道词典 (dict.youdao.com), A chinese translation website. +;; +;; This engine is only for chinese user. +;; > 此引擎仅供中文翻译使用 +;; +;; Simply implement using EWW to do the parsing job for now. I am too lazy to parse and extract the html results. +;; > 懒得去解析、组装 html,所以暂时直接借用 eww 的解析结果 +;; > 权作应急使用,*也许* 以后会增加更完善的 parser 方案 + +;;; Code: + +(require 'gts-implements) + +(defclass gts-youdao-dict-eww-parser (gts-parser) + ((tag :initform "EWW"))) + +(defclass gts-youdao-dict-engine (gts-engine) + ((tag :initform "有道词典") + (url :initform "https://dict.youdao.com/result?word=%s&lang=%s") + (parser :initform (gts-youdao-dict-eww-parser)))) + +(cl-defmethod gts-translate ((engine gts-youdao-dict-engine) task rendercb) + (with-slots (text from to meta) task + (with-slots (url parser) engine + (let* ((lang (cond ((string-equal from "zh") to) + ((string-equal to "zh") from) + (t (user-error "只支持中文跟其他语言之间的翻译")))) + (url (format url (url-hexify-string text) (url-hexify-string lang)))) + (setf meta (list :url url)) + (gts-do-request url + :done (lambda () + (gts-update-raw task (buffer-string)) ; html format response + (gts-parse parser task) + (funcall rendercb)) + :fail (lambda (err) + (gts-render-fail task err))))))) + +(cl-defmethod gts-parse ((_ gts-youdao-dict-eww-parser) task) + (with-slots (raw meta) task + (with-temp-buffer + (let ((buf (current-buffer))) + (with-temp-buffer + (require 'eww) + (insert "Content-type: text/html; charset=utf-8\n\n" raw) + (eww-render nil (plist-get meta :url) nil buf)) + (gts-update-parsed task (buffer-string) meta))))) + + +(provide 'gts-engine-youdao) + +;;; gts-engine-youdao.el ends here diff --git a/elpa/go-translate-20230503.303/gts-faces.el b/elpa/go-translate-20230503.303/gts-faces.el new file mode 100644 index 00000000..107b4ed9 --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-faces.el @@ -0,0 +1,124 @@ +;;; gts-faces.el --- Face definitions -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; 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) + + +;;; 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) + + +;;; 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) + + +;;; Posframe Render + +(defcustom gts-pop-posframe-forecolor "white" + "Default foreground color of pop posframe." + :type 'string + :group 'go-translate-faces) + +(defcustom gts-pop-posframe-backcolor "black" + "Default background color of pop posframe." + :type 'string + :group 'go-translate-faces) + +(defcustom gts-pin-posframe-forecolor nil + "Default foreground color of pin posframe." + :type 'string + :group 'go-translate-faces) + +(defcustom gts-pin-posframe-backcolor nil + "Default background color of pin posframe." + :type 'string + :group 'go-translate-faces) + +(defcustom gts-pin-posframe-bdcolor "#000000" + "Default border color of pin posframe." + :type 'string + :group 'go-translate-faces) + +(defcustom gts-pin-posframe-fringe-color nil + "Used in the posframe pip buffer fringe color." + :type 'string + :group 'go-translate-faces) + +(defface gts-pop-posframe-me-header-face '((t :foreground "grey")) + "Used in the posframe pop buffer engine header." + :group 'go-translate-faces) + +(defface gts-pop-posframe-me-header-2-face '((t (:foreground "#666666" :underline t :extend t))) + "Used in the posframe pop buffer engine header." + :group 'go-translate-faces) + + +(provide 'gts-faces) + +;;; gts-faces.el ends here diff --git a/elpa/go-translate-20230503.303/gts-implements.el b/elpa/go-translate-20230503.303/gts-implements.el new file mode 100644 index 00000000..b5ac3bf4 --- /dev/null +++ b/elpa/go-translate-20230503.303/gts-implements.el @@ -0,0 +1,661 @@ +;;; gts-implements.el --- Component implements -*- lexical-binding: t -*- + +;; Copyright (C) 2021 lorniu +;; SPDX-License-Identifier: MIT + +;;; Commentary: + +;; Builtin implements of basic components + +;;; Code: + +(require 'cl-lib) +(require 'url) +(require 'json) +(require 'gts-core) +(require 'gts-faces) + + +;;; [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)) + + +;;; [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)) + + +;;; [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 + + +;;; [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)))) + + +;;; [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"))) + + +;;; [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)) + + +;;; [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.")))) + + +;;; [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)))) + + +;;; [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) + + +;;; [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))) + + +;;; [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)))) + + +;;; [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))) + + +;;; [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