diff --git a/.gitignore b/.gitignore index 36a7586..4f86348 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ -elpa -eshell backup/ *.elc diff --git a/.gitmodules b/.gitmodules index 71004b4..8ac246e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "stuff/go-mode.el"] path = stuff/go.el url = https://github.com/dominikh/go-mode.el.git +[submodule "stuff/dockerfile-mode"] + path = stuff/dockerfile-mode + url = https://github.com/spotify/dockerfile-mode +[submodule "stuff/sass-mode"] + path = stuff/sass-mode + url = https://github.com/nex3/sass-mode diff --git a/configs/hooks.el b/configs/hooks.el index ff59073..d07aa65 100644 --- a/configs/hooks.el +++ b/configs/hooks.el @@ -1,20 +1,20 @@ ; Delete trailing whitespaces on save -(add-hook 'before-save-hook 'delete-trailing-whitespace) +(add-hook 'write-file-hooks 'delete-trailing-whitespace) ;; Mode to collapse code block (add-hook 'c-mode-common-hook (lambda () (hs-minor-mode 1))) -(add-hook 'lisp-mode-hook (lambda () (hs-minor-mode 1))) -(add-hook 'java-mode-hook (lambda () (hs-minor-mode 1))) -(add-hook 'python-mode-hook (lambda () (hs-minor-mode 1))) +(add-hook 'lisp-mode-hooks (lambda () (hs-minor-mode 1))) +(add-hook 'java-mode-hooks (lambda () (hs-minor-mode 1))) +(add-hook 'python-mode-hooks (lambda () (hs-minor-mode 1))) ; Auto insert C/C++ header guard -(add-hook 'find-file-hook +(add-hook 'find-file-hooks (lambda () (when (and (memq major-mode '(c-mode c++-mode)) (equal (point-min) (point-max)) (string-match ".*\\.hh?" (buffer-file-name))) (insert-header-guard) (goto-line 3) (insert "\n")))) -(add-hook 'find-file-hook +(add-hook 'find-file-hooks (lambda () (when (and (memq major-mode '(c-mode c++-mode)) (equal (point-min) (point-max)) (string-match ".*\\.cc?" (buffer-file-name))) (insert-header-inclusion)))) @@ -28,7 +28,18 @@ (insert-shebang-if-empty "/usr/bin/ruby"))) ; Golang -; Modern Go configuration now in packages.el using eglot and go-mode (add-hook 'go-mode-hook (lambda () (set (make-local-variable 'compile-command) "go build"))) + +(add-hook 'go-mode-hook + (lambda () + (when (buffer-empty-p) + (save-excursion + (goto-char (point-min)) + (insert "package \n\nimport (\n\t\n)\n"))))) + +(add-hook 'before-save-hook 'gofmt-before-save) + +(eval-after-load "go-mode" + '(require 'flymake-go)) diff --git a/configs/key-binding.el b/configs/key-binding.el index 54e2f9d..4133af7 100644 --- a/configs/key-binding.el +++ b/configs/key-binding.el @@ -41,6 +41,10 @@ (global-set-key (kbd "C-c k") 'kill-this-buffer) (put 'narrow-to-region 'disabled nil) +;; Magit +(global-set-key (kbd "C-x g") 'magit-status) +(global-set-key (kbd "C-x M-g") 'magit-dispatch-popup) + ;; Don't shift-selection (setq shift-select-mode nil) diff --git a/configs/modes.el b/configs/modes.el index eb2adad..c092bc6 100644 --- a/configs/modes.el +++ b/configs/modes.el @@ -18,76 +18,18 @@ (add-to-list 'auto-mode-alist '("\\.bbclass$" . conf-mode)) (add-to-list 'auto-mode-alist '("\\.bbappend$" . conf-mode)) +;; Go mode +(require 'go-mode-autoloads) + ;; Changelog mode (add-to-list 'auto-mode-alist '("COMMIT_EDITMSG" . change-log-mode)) -;; web-mode -;; Now configured in packages.el with LSP support and prettier auto-formatting - ;(require 'web-mode) - ;(add-to-list 'auto-mode-alist '("\\.svelte$" . web-mode)) - ;(setq web-mode-code-indent-offset 4) - ;(setq web-mode-css-indent-offset 4) - ;(setq web-mode-markup-indent-offset 4) - ;(setq web-mode-script-padding 4) - ;(setq web-mode-style-padding 4) - ;(setq web-mode-void-elements - ; '("area" "base" "br" "command" "embed" "hr" "img" "input" "keygen" - ; "link" "meta" "param" "source" "track" "wbr" "tmpl_var")) - -;; Vue.js-mode -(require 'vue-mode) -(add-to-list 'auto-mode-alist '("\\.vue$" . vue-mode)) - ;(setq mmm-js-mode-enter-hook (lambda () (setq syntax-ppss-table nil))) - ;(setq mmm-typescript-mode-enter-hook (lambda () (setq syntax-ppss-table nil))) -(defun fix-mmm-syntax () - (save-restriction - (setq-local syntax-ppss-table typescript-mode-syntax-table) - )) -(add-hook 'mmm-typescript-mode-enter-hook 'fix-mmm-syntax) -;; js-indent-level now configured in packages.el (set to 4 spaces) -;(setq js-indent-level 2) -(setq vue-html-extra-indent 2) -(setq-default indent-tabs-mode nil) - -(add-hook 'mmm-mode-hook - (lambda () - (set-face-background 'mmm-default-submode-face nil) - (add-to-list 'sgml-tag-alist '("b-col" \n ("cols" "6") ("sm" "6") ("md" "6") ("lg" "6") ("xl" "6") ("offset" "6") ("offset-sm" "6") ("offset-md" "6") ("offset-lg" "6") ("offset-xl" "6") ("order" "1") ("order-sm" "1") ("order-md" "1") ("order-lg" "1") ("order-xl" "1") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-container" \n ("fluid") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-row" \n ("no-gutters") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-form-row" \n ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-alert" \n ("variant" "info") ("dismissible") ("dismiss-label" "Close") ("show") ("fade") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-badge" \n ("href" "#") ("rel" "") ("target" "_self") ("active") ("disabled") ("to" "") ("append") ("replace") ("event" "click") ("active-class" "") ("exact") ("exact-active-class" "") ("router-tag" "a") ("no-prefetch") ("tag" "span") ("variant" "info") ("pill") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-breadcrumb" \n ("items" "") (":items" "{}") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-button" \n ("href" "#") ("rel" "") ("target" "_self") ("active") ("disabled") ("to" "") ("append") ("replace") ("event" "click") ("active-class" "") ("exact") ("exact-active-class" "") ("router-tag" "a") ("no-prefetch") ("block") ("size" "sm") ("variant" "info") ("type" "button") ("tag" "button") ("pill") ("squared") ("pressed") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-button-group" \n ("vertical") ("size" "sm") ("tag" "div") ("aria-role" "group") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-button-toolbar" \n ("justify") ("key-nav") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-calendar" \n ("id" "") ("value" "") ("value-as-date") ("initial-date" "") ("disabled") ("readonly") ("min" "") ("max" "") ("date-disabled-fn" "") ("start-weekday" "0") ("locale" "") ("direction" "") ("selected-variant" "primary") ("today-variant" "") ("no-highlight-today") ("date-info-fn" "") ("width" "270px") ("block") ("hide-header") ("show-decade-nav") ("hidden") ("aria-controls" "") ("role-description" "") ("label-prev-decade" "Previous decade") ("label-prev-year" "Previous year") ("label-prev-month" "Previous month") ("label-current-month" "Current month") ("label-next-month" "Next month") ("label-next-year" "Next year") ("label-next-decade" "Next decade") ("label-today" "Today") ("label-selected" "Selected date") ("label-no-date-selected" "No date selected") ("label-calendar" "Calendar") ("label-nav" "Calendar navigation") ("label-help" "Use cursor keys to navigate calendar dates") ("date-format-options" "{}") ("weekday-header-format" "short") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-form" \n ("id" "") ("inline") ("novalidate") ("validated") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-form-text" \n ("id" "") ("tag" "small") ("inline") ("text-variant" "muted") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-form-invalid-feedback" \n ("id" "") ("tag" "small") ("tooltip") ("force-show") ("state") ("aria-live" "") ("role" "") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-form-valid-feedback" \n ("id" "") ("tag" "small") ("tooltip") ("force-show") ("state") ("aria-live" "") ("role" "") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-form-datalist" \n (":options" "{}") ("value-field" "value") ("text-field" "text") ("html-field" "html") ("disabled-field" "disabled") ("id" "") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-form-input" \n ("id" "") ("name" "") ("disabled") ("required") ("form" "") ("autofocus") ("size" "sm") ("state") ("value" "") ("aria-invalid") ("readonly") ("plaintext") ("autocomplete" "") ("placeholder" "") ("formatter" "") ("lazy-formatter") ("trim") ("number") ("lazy") ("debounce" "0") ("type" "text") ("no-wheel") ("min" "") ("max" "") ("step" "") ("list" "{}") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-input-group" \n ("id" "") ("size" "sm") ("prepend" "") ("prepend-html" "") ("append" "") ("append-html" "") ("tag" "div") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-input-group-prepend" \n ("id" "") ("tag" "div") ("is-text") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-input-group-append" \n ("id" "") ("tag" "div") ("is-text") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-input-group-text" \n ("tag" "div") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-input-group-addon" \n ("id" "") ("tag" "div") ("is-text") ("append") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-nav" \n ("tag" "ul") ("fill") ("justified") ("align" "") ("tabs") ("pills") ("vertical") ("small") ("card-header") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-nav-item" \n ("href" "") ("rel" "") ("target" "") ("active") ("disabled") ("to" "") ("append") ("replace") ("event" "") ("active-class" "") ("exact") ("exact-active-class" "") ("router-tag" "a") ("no-prefetch") ("link-attrs" "{}") ("link-classes" "") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-nav-text" \n ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-nav-form" \n ("id" "") ("novalidate") ("validated") ("form-class" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-nav-item-dropdown" \n ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-navbar" \n ("tag" "nav") ("type" "light") ("variant" "") ("toggleable") ("fixed" "") ("sticky") ("print") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-navbar-nav" \n ("tag" "ul") ("fill") ("justified") ("align" "") ("small") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-navbar-brand" \n ("href" "#") ("ref" "") ("target" "_self") ("active") ("disabled") ("to" "") ("replace") ("event" "click") ("active-class" "") ("exact") ("exact-active-class" "") ("router-tag" "a") ("no-prefetch") ("tag" "div") ("v-model" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-navbar-toggle" \n ("label" "") ("target" "") ("v-if" "") ("v-for" ""))) - (add-to-list 'sgml-tag-alist '("b-spinner" \n ("type" "border") ("label" "") ("variant" "primary") ("small") ("role" "status") ("tag" "span") ("v-if" "") ("v-for" ""))) - ) - ) +;; Edje-mode +(require 'edje-mode) +(add-to-list 'auto-mode-alist '("\\.edc$" . edje-mode)) ;; Org-mode +(require 'org-install) (add-to-list 'auto-mode-alist '("\\.org$" . org-mode)) (define-key global-map "\C-cl" 'org-store-link) (define-key global-map "\C-ca" 'org-agenda) @@ -101,3 +43,22 @@ ;; Markdown-mode (add-to-list 'auto-mode-alist '("\\.md$" . markdown-mode)) + +;; Python-mode +;(when (load "flymake" t) +; (defun flymake-pyflakes-init () +; (let* ((temp-file (flymake-init-create-temp-buffer-copy +; 'flymake-create-temp-inplace)) +; (local-file (file-relative-name +; temp-file +; (file-name-directory buffer-file-name)))) +; (list "pyflakes" (list local-file)))) +; +; (add-to-list 'flymake-allowed-file-name-masks +; '("\\.py\\'" flymake-pyflakes-init))) +;(add-hook 'find-file-hook 'flymake-find-file-hook) + +;; mmm-mode +(add-hook 'mmm-mode-hook + (lambda () + (set-face-background 'mmm-default-submode-face nil))) diff --git a/configs/project.el b/configs/project.el new file mode 100644 index 0000000..9df1e67 --- /dev/null +++ b/configs/project.el @@ -0,0 +1,17 @@ +(setq project-roots + `(("Django project" + :root-contains-files ("manage.py") + :filename-regex ,(regexify-ext-list '(py html css js sh)) + :exclude-paths '("contrib")))) + +(global-set-key (kbd "C-c p f") 'project-root-find-file) +(global-set-key (kbd "C-c p g") 'project-root-grep) +(global-set-key (kbd "C-c p a") 'project-root-ack) +(global-set-key (kbd "C-c p d") 'project-root-goto-root) +(global-set-key (kbd "C-c p l") 'project-root-browse-seen-projects) + +(global-set-key (kbd "C-c p s") + (lambda () (interactive) + (with-project-root + (ansi-term (getenv "SHELL") + (concat (car project-details) "-shell"))) diff --git a/init.el b/init.el index 8d2f090..40ba2b3 100644 --- a/init.el +++ b/init.el @@ -3,45 +3,6 @@ ;; ;; Made by Némunaire -(require 'site-gentoo) - -(require 'package) -(add-to-list 'package-archives - '("MELPA Stable" . "https://stable.melpa.org/packages/") t) - -;; Added by Package.el. This must come before configurations of -;; installed packages. Don't delete this line. If you don't want it, -;; just comment it out by adding a semicolon to the start of the line. -;; You may delete these explanatory comments. - -;; Initialize package system but don't activate packages yet -(setq package-enable-at-startup nil) -(package-initialize) - -;; Register system-installed vterm in package-alist -;; (site-gentoo only adds to load-path, not package-alist) -(unless (assq 'vterm package-alist) - (let ((desc (package-desc-create - :name 'vterm - :version '(0 0 2) - :summary "Emulation of a terminal" - :reqs '((emacs (25 1))) - :kind 'dir - :dir "/usr/share/emacs/site-lisp/vterm"))) - (push (cons 'vterm (list desc)) package-alist))) - -;; Now activate all packages (including claude-code which will find vterm) -(package-activate-all) - -;; Bootstrap use-package -(unless (package-installed-p 'use-package) - (package-refresh-contents) - (package-install 'use-package)) -(require 'use-package) -(setq use-package-always-ensure t) - -(setq flymake-allowed-file-name-masks nil) - (defun may-load (path) "Load a file if it exists." (when (file-readable-p path) @@ -81,10 +42,11 @@ (require 'my-layout) (require 'my-lisp-mode) (require 'my-python-mode) +(require 'vue-mode) ;; load my configuration files -(toc:load-config-file '("packages" ; Modern package management (load first) - "key-binding" +(toc:load-config-file '("key-binding" +;; "project" "editing" "coding-style" "tags" @@ -94,8 +56,3 @@ "custom" "perso" )) -(custom-set-variables - '(package-selected-packages nil) - '(python-indent 4) - '(query-user-mail-address nil) - '(user-mail-address "nemunaire@nemunai.re")) diff --git a/stuff/edje-mode.el b/stuff/edje-mode.el new file mode 100644 index 0000000..33c6fe6 --- /dev/null +++ b/stuff/edje-mode.el @@ -0,0 +1,512 @@ +;;; edje-mode-el -- Major mode for editing Edje files + +;; Author: Gustavo Sverzut Barbieri +;; Created: 2007-07-23 +;; Keywords: Edje major-mode +;; Url: http://barbieri-playground.googlecode.com/svn/dot-files/edje-mode.el +;; (if you find this file have problems, check that Url and request update) + +;; Copyright (C) 2007 Gustavo Sverzut Barbieri + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of +;; the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be +;; useful, but WITHOUT ANY WARRANTY; without even the implied +;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +;; PURPOSE. See the GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public +;; License along with this program; if not, write to the Free +;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +;; MA 02111-1307 USA + +;;; Commentary: +;; +;; This mode is based on tutorial from Scott Andrew Borton: +;; http://two-wugs.net/emacs/mode-tutorial.html + + +(defvar edje-mode-hook nil) + +(defun number-or-nil-to-string (v &optional default) + (cond ((numberp v) (number-to-string v)) + ((stringp v) (if (string= v "") (number-to-string default) v)) + (t (number-to-string default)))) + +(defun non-empty-string (s) + (and (not (eq 'nil s)) + (not (string= "" s)))) + +(defun edje-new-program-action-signal-emit (source emission) + "Insert new program SIGNAL_EMIT" + (interactive "ssource: \nsemission: ") + (insert + (concat + " action: SIGNAL_EMIT \"" source "\" \"" emission "\";\n" + ))) + +(defun edje-new-program-action-state-set (state value target) + "Insert new program STATE_SET" + (interactive "sstate: \nsvalue (0.0): \nstarget: ") + (insert + (concat + " action: STATE_SET \"" state "\" " + (number-or-nil-to-string value 0.0) ";\n" + " target: \"" target "\";\n" + ))) + +(defun edje-new-program-action (action) + "Insert new program action" + (interactive "saction: ") + (setq action (upcase action)) + (cond ((string= action "STATE_SET") + (edje-new-program-action-state-set "" 0.0 "")) + ((string= action "SIGNAL_EMIT") + (edje-new-program-action-signal-emit "" "")) + )) + +(defun edje-new-program (name signal source action) + "Insert new program block" + (interactive "sname: \nssignal: \nssource: \nsaction: ") + (insert + (concat + "\n" + " program {\n" + " name: \"" name "\";\n" + + (if (non-empty-string signal) + (concat " signal: \"" signal "\";\n")) + + (if (non-empty-string source) + (concat " source: \"" source "\";\n")) + )) + + (edje-new-program-action action) + + (insert + (concat + " }\n" + "\n" + ))) + +(defun edje-new-desc-relative (x y &optional defx defy) + "Insert new part description 'relative' line" + (interactive "sx: \nsy: ") + (insert + (concat + " relative: " + (number-or-nil-to-string x defx) " " + (number-or-nil-to-string y defy) ";\n" + ))) + +(defun edje-new-desc-offset (x y &optional defx defy) + "Insert new part description 'offset' line" + (interactive "sx: \nsy: ") + (insert + (concat + " offset: " + (number-or-nil-to-string x defx) " " + (number-or-nil-to-string y defy) ";\n" + ))) + +(defun edje-new-desc-inherit (name val) + "Insert new part description 'inherit' line" + (interactive "sname: \nsvalue: ") + (insert + (concat + " inherit: \"" name "\" " + (number-or-nil-to-string val 0.0) ";\n" + ))) + +(defun edje-new-desc-text (font size text) + "Insert new part description 'text' block" + (interactive "sfont: \nssize: \nstext: ") + (insert + (concat + " text {\n" + " font: \"" font "\";\n" + " size: " (number-or-nil-to-string size) ";\n" + " text: \"" text "\";\n" + " }\n" + ))) + +(defun edje-new-desc-image (name) + "Insert new part description 'image' block" + (interactive "sname: ") + (insert + (concat + " image {\n" + " normal: \"" name "\";\n" + " }\n" + ))) + +(defun edje-new-desc-color (r g b a &optional defr defg defb defa) + "Insert new part description 'color' line" + (interactive "sred: \nsgreen: \nsblue: \nsalpha: ") + (insert + (concat + " color: " + (number-or-nil-to-string r defr) " " + (number-or-nil-to-string g defg) " " + (number-or-nil-to-string b defb) " " + (number-or-nil-to-string a defa) ";\n" + ))) + +(defun edje-new-desc (name val &optional + r1_rx r1_ry + r2_rx r2_ry + r1_ox r1_oy + r2_ox r2_oy + part_type) + "Insert new part description block" + (interactive "sName: \nsValue: ") + (insert + (concat + " description {\n" + " state: \"" name "\" " (number-or-nil-to-string val 0.0) ";\n")) + (if (string= part_type "RECT") (edje-new-desc-color 255 255 255 255)) + (insert " rel1 {\n") + (edje-new-desc-relative r1_rx r1_ry 0.0 0.0) + (edje-new-desc-offset r1_ox r1_oy 0 0) + (insert + (concat + " }\n" + " rel2 {\n" + )) + (edje-new-desc-relative r2_rx r2_ry 1.0 1.0) + (edje-new-desc-offset r2_ox r2_oy -1 -1) + (insert " }\n") + (cond ((string= part_type "IMAGE") (edje-new-desc-image "")) + ((string= part_type "TEXT") (edje-new-desc-text "" 10 "contents")) + ) + (insert " }\n") + ) + +(defun edje-new-part (name type &optional + r1_rx r1_ry + r2_rx r2_ry + r1_ox r1_oy + r2_ox r2_oy) + "Insert new part" + (interactive "sName: \nsType: ") + (setq type (upcase type)) + (insert + (concat + "\n" + " part {\n" + " name: \"" name "\";\n" + " type: " type ";\n" + " mouse_events: 0;\n" + )) + (edje-new-desc "default" 0.0 r1_rx r1_ry r2_rx r2_ry r1_ox r1_oy r2_ox r2_oy type) + (insert + (concat + " }\n" + ))) + +(defun edje-setup-compile () + (set (make-local-variable 'compile-command) + (concat "edje_cc " (buffer-file-name)) + )) + +(defun edje-cc () + "Runs edje_cc with current buffer." + (interactive) + (compile (edje-setup-compile))) + +(defvar edje-mode-map + (let ((edje-mode-map (make-sparse-keymap))) + (define-key edje-mode-map "\C-j" 'newline-and-indent) + (define-key edje-mode-map "\C-cp" 'edje-new-part) + (define-key edje-mode-map "\C-cd" 'edje-new-desc) + (define-key edje-mode-map "\C-cr" 'edje-new-desc-relative) + (define-key edje-mode-map "\C-co" 'edje-new-desc-offset) + (define-key edje-mode-map "\C-ch" 'edje-new-desc-inherit) + (define-key edje-mode-map "\C-cc" 'edje-new-desc-color) + (define-key edje-mode-map "\C-ci" 'edje-new-desc-image) + (define-key edje-mode-map "\C-ct" 'edje-new-desc-text) + (define-key edje-mode-map "\C-cg" 'edje-new-program) + (define-key edje-mode-map "\C-ca" 'edje-new-program-action) + (define-key edje-mode-map "\C-cs" 'edje-new-program-action-state-set) + (define-key edje-mode-map "\C-ce" 'edje-new-program-action-signal-emit) + edje-mode-map) + "Keymap for Edje major mode") + +(add-hook 'c-mode-hook 'edje-setup-compile) +(add-to-list 'auto-mode-alist '("\\.edc$" . edje-mode)) + +(defconst edje-font-lock-keywords-1 + (eval-when-compile + (list + (list (concat "[ \t]*\\<" + (regexp-opt + '( + "collections" + "data" + "description" + "dragable" + "fill" + "fonts" + "group" + "image" + "images" + "origin" + "part" + "parts" + "program" + "programs" + "rel1" + "rel2" + "script" + "spectra" + "style" + "styles" + "text" + ) t) "\\>\\([ \t]*{\\|\\.\\)") + '(1 font-lock-function-name-face)) + + )) + "Major keywords") + +(defconst edje-font-lock-keywords-2 + (eval-when-compile + (append edje-font-lock-keywords-1 + (list + (list + (concat "^\\([ \t]*\\|[ \t]*[a-z]+\\.\\|\\)\\<" + (regexp-opt + '("action" + "after" + "alias" + "align" + "angle" + "aspect" + "aspect_preference" + "base" + "border" + "clip_to" + "collections" + "color" + "color2" + "color3" + "color_class" + "color_classes" + "confine" + "data" + "description" + "dragable" + "effect" + "elipsis" + "events" + "fill" + "fit" + "fixed" + "font" + "fonts" + "gradient" + "group" + "ignore_flags" + "image" + "images" + "in" + "inherit" + "item" + "max" + "middle" + "min" + "mouse_events" + "name" + "normal" + "offset" + "origin" + "part" + "parts" + "pointer_mode" + "precise_is_inside" + "program" + "programs" + "rel1" + "rel2" + "relative" + "repeat_events" + "signal" + "size" + "smooth" + "source" + "spectra" + "spectrum" + "spread" + "state" + "step" + "style" + "styles" + "tag" + "target" + "text" + "text_class" + "text_source" + "to" + "to_x" + "to_y" + "transition" + "tween" + "type" + "use_alternate_font_metrics" + "visible" + "x" + "y" + ) t) "\\>[ \t]*[:,]") + '(2 font-lock-keyword-face)) + ))) + "Minor keywords") + +(defconst edje-font-lock-keywords-3 + (eval-when-compile + (append edje-font-lock-keywords-2 + (list + (list + (concat "\\<" + (regexp-opt + '(; image options (st_images_image) + "RAW" + "COMP" + "LOSSY" + "USER" + ; part types (st_collections_group_parts_part_type) + "NONE" + "RECT" + "TEXT" + "IMAGE" + "SWALLOW" + "TEXTBLOCK" + "GRADIENT" + "GROUP" + ; ignore flags (st_collections_group_parts_part_ignore_flags) + ;"NONE" + "ON_HOLD" + ; pointer mode (st_collections_group_parts_part_pointer_mode) + "AUTOGRAB" + "NOGRAB" + ; aspect (st_collections_group_parts_part_description_aspect_preference) + "NONE" + "VERTICAL" + "HORIZONTAL" + "BOTH" + ; text effect (st_collections_group_parts_part_effect) + "NONE" + "PLAIN" + "OUTLINE" + "SOFT_OUTLINE" + "SHADOW" + "SOFT_SHADOW" + "OUTLINE_SHADOW" + "OUTLINE_SOFT_SHADOW" + "FAR_SHADOW" + "FAR_SOFT_SHADOW" + "GLOW" + ; image fill (st_collections_group_parts_part_description_fill_type) + "SCALE" + "TILE" + ; program action (st_collections_group_programs_program_action) + "STATE_SET" + "ACTION_STOP" + "SIGNAL_EMIT" + "DRAG_VAL_SET" + "DRAG_VAL_STEP" + "DRAG_VAL_PAGE" + "SCRIPT" + ; program transition (st_collections_group_programs_program_transition) + "LINEAR" + "SINUSOIDAL" + "ACCELERATE" + "DECELERATE" + ) t) "\\>") + '(1 font-lock-builtin-face)) + ))) + "Enumerate values") + +(defconst edje-font-lock-keywords-4 + (eval-when-compile + (append edje-font-lock-keywords-3 + (list + (list + (concat "[ \t]*#" + (regexp-opt + '("if" + "ifdef" + "ifndef" + "define" + "else" + "endif" + "include" + "undef") t) "[ \t]*") + '(1 font-lock-builtin-face)) + ))) + "CPP directives") + +(defconst edje-font-lock-keywords-5 + (eval-when-compile + (append edje-font-lock-keywords-4 + (list + (list "[ \t]*#undef[ \t]+\\([a-zA-Z_][a-zA-Z0-9_]*\\)" + '(1 font-lock-variable-name-face)) + (list "[ \t]*#define[ \t]+\\([a-zA-Z_][a-zA-Z0-9_]*\\)(" + '(1 font-lock-function-name-face)) + (list "[ \t]*#define[ \t]+\\([a-zA-Z_][a-zA-Z0-9_]*\\)" + '(1 font-lock-variable-name-face)) + ))) + "CPP directives that define constants") + + +(defvar edje-font-lock-keywords edje-font-lock-keywords-5) + +(defvar edje-mode-syntax-table + (let ((edje-mode-syntax-table (make-syntax-table))) + ; This is added so entity names with underscores can be more easily parsed + (modify-syntax-entry ?_ "w" edje-mode-syntax-table) + (modify-syntax-entry ?/ ". 124b" edje-mode-syntax-table) + (modify-syntax-entry ?* ". 23" edje-mode-syntax-table) + (modify-syntax-entry ?\n "> b" edje-mode-syntax-table) + + edje-mode-syntax-table) + "Syntax table for edje-mode") + +(c-add-style + "edje" + '("gnu" + (indent-tabs-mode . nil) + (tab-width . 8) + (c-basic-offset . 3) + (c-backslash-column . 72) + (c-hanging-braces-alist . + ((block-open after) + (brace-list-open after) + (substatement-open after)) + ) + (c-offsets-alist . + ((statement-block-intro . +) + (defun-open . 0) + (substatement-open . 0) + (defun-block-intro . +) + (block-open . 0) + (label . +) + )))) + + +(define-derived-mode edje-mode c-mode "Edje" + "Major mode for editing Edje files" + (interactive) + (use-local-map edje-mode-map) + (set-syntax-table edje-mode-syntax-table) + (set (make-local-variable 'font-lock-defaults) '(edje-font-lock-keywords)) + (set (make-local-variable 'require-final-newline) t) + (c-set-style "edje") + (run-hooks 'edje-mode-hook) + ) + +(provide 'edje-mode) + +;;; edje-mode.el ends here diff --git a/stuff/find-cmd.el b/stuff/find-cmd.el new file mode 100644 index 0000000..877b96c --- /dev/null +++ b/stuff/find-cmd.el @@ -0,0 +1,242 @@ +;;; find-cmd.el --- Build a valid find(1) command with sexps + +;; Copyright (C) 2008, 2009 Free Software Foundation, Inc. + +;; Author: Philip Jackson +;; Version: 0.6 + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; With this module you can build up a (hopefully) valid find(1) +;; string ready for the command line. For example: + +;; (find-cmd '(prune (name ".svn" ".git" ".CVS")) +;; '(and (or (name "*.pl" "*.pm" "*.t") +;; (mtime "+1")) +;; (fstype "nfs" "ufs")))) + +;; will become (un-wrapped): + +;; "find '/home/phil/' \\( \\( -name '.svn' -or -name '.git' -or +;; -name '.CVS' \\) -prune -or -true \\) \\( \\( \\( -name '*.pl' +;; -or -name '*.pm' -or -name '*.t' \\) -or -mtime '+1' \\) -and \\( +;; -fstype 'nfs' -or -fstype 'ufs' \\) \\)" + +;;; Code: + +(defconst find-constituents + '((and . find-and) + (not . find-not) + (or . find-or) + + (a . find-and) + (n . find-not) + (o . find-or) + + (prune . find-prune) + + ;; switches + (L . (0)) + (P . (0)) + (H . (0)) + + ;; generic tests + (amin . (1)) + (anewer . (1)) + (atime . (1)) + (cmin . (1)) + (cnewer . (1)) + (ctime . (1)) + (empty . (0)) + (false . (0)) + (fstype . (1)) + (gid . (1)) + (group . (1)) + (ilname . (1)) + (iname . (1)) + (inum . (1)) + (iwholename . (1)) + (iregex . (1)) + (links . (1)) + (lname . (1)) + (mmin . (1)) + (mtime . (1)) + (name . (1)) + (newer . (1)) + (nouser . (0)) + (nogroup . (0)) + (path . (1)) + (perm . (0)) + (regex . (1)) + (wholename . (1)) + (size . (1)) + (true . (0)) + (type . (1)) + (uid . (1)) + (used . (1)) + (user . (1)) + (xtype . (nil)) + + ;; normal options (always true) + (depth . (0)) + (maxdepth . (1)) + (mindepth . (1)) + (mount . (0)) + (noleaf . (0)) + (xdev . (0)) + (ignore_readdir_race . (0)) + (noignore_readdir_race . (0)) + + ;; actions + (delete . (0)) + (print0 . (0)) + (printf . (1)) + (fprintf . (2)) + (print . (0)) + (fprint0 . (1)) + (fprint . (1)) + (ls . (0)) + (fls . (1)) + (prune . (0)) + (quit . (0)) + + ;; these need to be terminated with a ; + (exec . (1 find-command t)) + (ok . (1 find-command t)) + (execdir . (1 find-command t)) + (okdir . (1 find-command t))) + "Holds details of each of the find options. The car of each +alist is the name. The cdr is minimum args, the function used +to join many occurences of the argument together, and whether or +not to leave quotes off the string (non-nil means the string will +be quoted).") + +;;;###autoload +(defun find-cmd (&rest subfinds) + "Initiate the building of a find command. For exmple: + +\(find-cmd '\(prune \(name \".svn\" \".git\" \".CVS\"\)\) + '\(and \(or \(name \"*.pl\" \"*.pm\" \"*.t\"\) + \(mtime \"+1\"\)\) + \(fstype \"nfs\" \"ufs\"\)\)\)\) + +`default-directory' is used as the initial search path. The +result is a string that should be ready for the command line." + (concat + "find " (shell-quote-argument (expand-file-name default-directory)) " " + (cond + ((cdr subfinds) + (mapconcat 'find-to-string subfinds "")) + (t + (find-to-string (car subfinds)))))) + +(defun find-and (form) + "And FORMs together, so: + \(and \(mtime \"+1\"\) \(name \"something\"\)\) +will produce: + find . \\\( -mtime '+1' -and -name 'something' \\\)" + (if (< (length form) 2) + (find-to-string (car form)) + (concat "\\( " + (mapconcat 'find-to-string form "-and ") + "\\) "))) + +(defun find-or (form) + "Or FORMs together, so: + \(or \(mtime \"+1\"\) \(name \"something\"\)\) +will produce: + find . \\\( -mtime '+1' -or -name 'something' \\\)" + (if (< (length form) 2) + (find-to-string (car form)) + (concat "\\( " + (mapconcat 'find-to-string form "-or ") + "\\) "))) + +(defun find-not (form) + "Or FORMs together and prefix with a -not, so: + \(not \(mtime \"+1\"\) \(name \"something\"\)\) +will produce: + -not \\\( -mtime '+1' -or -name 'something' \\\) +If you wanted the FORMs -and(ed) together instead then this would +suffice: + \(not \(and \(mtime \"+1\"\) \(name \"something\"\)\)\)" + (concat "-not " (find-or (mapcar 'find-to-string form)))) + +(defun find-prune (form) + "-or together FORM(s) postfix '-prune' and then -or that with a +-true, so: + \(prune \(name \".svn\" \".git\"\)\) \(name \"*.pm\"\) +will produce (unwrapped): + \\\( \\\( \\\( -name '.svn' -or -name '.git' \\\) / + -prune -or -true \\\) -and -name '*.pm' \\\)" + (find-or + (list + (concat (find-or (mapcar 'find-to-string form)) (find-generic "prune")) + (find-generic "true")))) + +(defun find-generic (option &optional oper argcount args dont-quote) + "This function allows an arbitrary string to be used as a +form. OPTION is the name of the form, OPER is the function used +to either OR or AND multiple results together. ARGCOUNT is the +minimum of args that OPTION can receive and ARGS are the +arguments for OPTION." + (when (and (numberp argcount) (< (length args) argcount)) + (error "'%s' needs at least %d arguments" option argcount)) + (let ((oper (or oper 'find-or))) + (if (and args (length args)) + (funcall oper (mapcar (lambda (x) + (concat "-" option + (if dont-quote + (concat " " x " ") + (concat " " + (shell-quote-argument x) + " ")))) + args)) + (concat "-" option " ")))) + +(defun find-command (form) + "For each item in FORM add a terminating semi-colon and turn +them into valid switches. The result is -and(ed) together." + (find-and (mapcar (lambda (x) + (concat (find-to-string x) "\\; ")) + form))) + +(defun find-to-string (form) + "Parse FORM to produce a set of valid find arguments." + (cond + ((stringp form) + form) + ((consp form) + (let ((option (cdr (assoc (car form) find-constituents)))) + (cond + ((and (symbolp option) (fboundp option)) + (funcall option (cdr form))) + ((consp option) + (let ((option (symbol-name (car form))) + (argcnt (car option)) + (oper (cadr option)) + (dont-quote (car (cddr option)))) + (find-to-string + (find-generic option oper argcnt (cdr form) dont-quote)))) + (t + (error "Sorry I don't know how to handle '%s'" (car form)))))))) + +(provide 'find-cmd) + +;; arch-tag: 9687fd9e-4e90-4022-864a-f904526e2046 +;;; find-cmd.el ends here diff --git a/stuff/flymake-cursor.el b/stuff/flymake-cursor.el new file mode 100644 index 0000000..0f0b8c5 --- /dev/null +++ b/stuff/flymake-cursor.el @@ -0,0 +1,167 @@ +;;; flymake-cursor.el --- displays flymake error msg in minibuffer after delay +;; +;; Author : ?? +;; origin : http://paste.lisp.org/display/60617,1/raw +;; Maintainer : Dino Chiesa +;; : Donald Curtis +;; Created : May 2011 +;; Modified : December 2012 +;; Version : 0.1.5 +;; Keywords : languages mode flymake +;; X-URL : http://www.emacswiki.org/emacs/flymake-cursor.el +;; Last-saved : <2012-Dec-20 09:49:28> +;; +;; ------------------------------------------------------- +;; +;; License: None. This code is in the Public Domain. +;; +;; +;; Additional functionality that makes flymake error messages appear +;; in the minibuffer when point is on a line containing a flymake +;; error. This saves having to mouse over the error, which is a +;; keyboard user's annoyance. +;; ------------------------------------------------------- +;; +;; This flymake-cursor module displays the flymake error in the +;; minibuffer, after a short delay. It is based on code I found roaming +;; around on the net, unsigned and unattributed. I suppose it's public +;; domain, because, while there is a "License" listed in it, there +;; is no license holder, no one to own the license. +;; +;; This version is modified slightly from that code. The post-command fn +;; defined in this code does not display the message directly. Instead +;; it sets a timer, and when the timer fires, the timer event function +;; displays the message. +;; +;; The reason to do this: the error message is displayed only if the +;; user doesn't do anything, for about one second. This way, if the user +;; scrolls through a buffer and there are myriad errors, the minibuffer +;; is not constantly being updated. +;; +;; If the user moves away from the line with the flymake error message +;; before the timer expires, then no error is displayed in the minibuffer. +;; +;; I've also updated the names of the defuns. They all start with flyc now. +;; +;; To use this, include this line in your .emacs: +;; +;; ;; enhancements for displaying flymake errors +;; (require 'flymake-cursor) +;; +;; You can, of course, put that in an eval-after-load clause. +;; +;; ------------------------------------------------------- +;; +;; Update 2012-03-06 by Donald Curtis +;; -- +;; Added some autoload statements and the closing comment to make +;; compatible with package.el parser. +;; +;; Update 2012-12-20 by Jeremy Moore +;; -- +;; Alter post-command-hook's local value via add-hook so that it plays +;; nicely with other packages. +;; + + +(require 'cl) + +(defvar flyc--e-at-point nil + "Error at point, after last command") + +(defvar flyc--e-display-timer nil + "A timer; when it fires, it displays the stored error message.") + +(defun flyc/maybe-fixup-message (errore) + "pyflake is flakey if it has compile problems, this adjusts the +message to display, so there is one ;)" + (cond ((not (or (eq major-mode 'Python) (eq major-mode 'python-mode) t))) + ((null (flymake-ler-file errore)) + ;; normal message do your thing + (flymake-ler-text errore)) + (t ;; could not compile error + (format "compile error, problem on line %s" (flymake-ler-line errore))))) + +(defun flyc/show-stored-error-now () + "Displays the stored error in the minibuffer." + (interactive) + (let ((editing-p (= (minibuffer-depth) 0))) + (if (and flyc--e-at-point editing-p) + (progn + (message "%s" (flyc/maybe-fixup-message flyc--e-at-point)) + (setq flyc--e-display-timer nil))))) + + +(defun flyc/-get-error-at-point () + "Gets the first flymake error on the line at point." + (let ((line-no (line-number-at-pos)) + flyc-e) + (dolist (elem flymake-err-info) + (if (eq (car elem) line-no) + (setq flyc-e (car (second elem))))) + flyc-e)) + + +;;;###autoload +(defun flyc/show-fly-error-at-point-now () + "If the cursor is sitting on a flymake error, display +the error message in the minibuffer." + (interactive) + (if flyc--e-display-timer + (progn + (cancel-timer flyc--e-display-timer) + (setq flyc--e-display-timer nil))) + (let ((error-at-point (flyc/-get-error-at-point))) + (if error-at-point + (progn + (setq flyc--e-at-point error-at-point) + (flyc/show-stored-error-now))))) + + +;;;###autoload +(defun flyc/show-fly-error-at-point-pretty-soon () + "If the cursor is sitting on a flymake error, grab the error, +and set a timer for \"pretty soon\". When the timer fires, the error +message will be displayed in the minibuffer. + +This allows a post-command-hook to NOT cause the minibuffer to be +updated 10,000 times as a user scrolls through a buffer +quickly. Only when the user pauses on a line for more than a +second, does the flymake error message (if any) get displayed. + +" + (if flyc--e-display-timer + (cancel-timer flyc--e-display-timer)) + + (let ((error-at-point (flyc/-get-error-at-point))) + (if error-at-point + (setq flyc--e-at-point error-at-point + flyc--e-display-timer + (run-at-time "0.9 sec" nil 'flyc/show-stored-error-now)) + (setq flyc--e-at-point nil + flyc--e-display-timer nil)))) + + +;;;###autoload +(eval-after-load "flymake" + '(progn + + (defadvice flymake-goto-next-error (after flyc/display-message-1 activate compile) + "Display the error in the mini-buffer rather than having to mouse over it" + (flyc/show-fly-error-at-point-now)) + + (defadvice flymake-goto-prev-error (after flyc/display-message-2 activate compile) + "Display the error in the mini-buffer rather than having to mouse over it" + (flyc/show-fly-error-at-point-now)) + + (defadvice flymake-mode (before flyc/post-command-fn activate compile) + "Add functionality to the post command hook so that if the +cursor is sitting on a flymake error the error information is +displayed in the minibuffer (rather than having to mouse over +it)" + (add-hook 'post-command-hook 'flyc/show-fly-error-at-point-pretty-soon t t)))) + + +(provide 'flymake-cursor) + +;;; flymake-cursor.el ends here diff --git a/stuff/go-mode-autoloads.el b/stuff/go-mode-autoloads.el new file mode 100644 index 0000000..96b27d8 --- /dev/null +++ b/stuff/go-mode-autoloads.el @@ -0,0 +1,98 @@ +;;; go-mode-autoloads.el --- automatically extracted autoloads +;; +;;; Code: + + +;;;### (autoloads (go-download-play godoc gofmt-before-save go-mode) +;;;;;; "go-mode" "go-mode.el" (21514 38760 682820 85000)) +;;; 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' +- `go-import-add' +- `go-remove-unused-imports' +- `go-goto-imports' +- `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. + +\(fn)" t nil) + +(autoload 'godoc "go-mode" "\ +Show Go documentation for a query, much like M-x man. + +\(fn QUERY)" t nil) + +(autoload 'go-download-play "go-mode" "\ +Downloads a paste from the playground and inserts it in a Go +buffer. Tries to look for a URL at point. + +\(fn URL)" t nil) + +;;;*** + +(provide 'go-mode-autoloads) +;; 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/stuff/go-mode.el b/stuff/go-mode.el new file mode 100644 index 0000000..e569aa0 --- /dev/null +++ b/stuff/go-mode.el @@ -0,0 +1,2009 @@ +;;; 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 +;; 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." + ;; 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. + (condition-case nil + (backward-up-list) + (scan-error nil))) + + +(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 "\\)")) + +;; Maximum number of identifiers that can be highlighted as type names +;; in one function type/declaration. +(defconst go--font-lock-func-param-num-groups 16) + +(defvar go-dangling-cache) +(defvar go-godoc-history nil) +(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-native + "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-native' uses +elisp to find all .a files in all /pkg/ directories. +`go-packages-go-list' uses 'go list all' to determine all Go +packages. `go-packages-go-list' generally produces more accurate +results, but can be slower than `go-packages-native'." + :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 godoc-command "go doc" + "Which executable to use for `godoc'. +This can either be 'godoc' or 'go doc', both as an absolute path +or an executable in PATH." + :type 'string + :group 'go) + +(defcustom godoc-and-godef-command "godoc" + "Which executable to use for `godoc' in `godoc-and-godef-command'. +Must be 'godoc' and not 'go doc' and can be 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-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" (shell-quote-argument (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 + `((go--match-func + ,@(mapcar (lambda (x) `(,x font-lock-type-face)) + (number-sequence 1 go--font-lock-func-param-num-groups))) + (,(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) + (,go-func-regexp 1 font-lock-function-name-face)) ;; function (not method) name + + (if go-fontify-function-calls + `((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-function-name-face) ;; function call/method name + (,(concat "[^[:word:][:multibyte:]](\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock-function-name-face)) ;; bracketed function call + `((,go-func-meth-regexp 2 font-lock-function-name-face))) ;; method name + + `( + ("\\(`[^`]*`\\)" 1 font-lock-multiline) ;; raw string literal, needed for font-lock-syntactic-keywords + (,(concat "\\_[[:space:]]+\\([^[:space:](]+\\)") 1 font-lock-type-face) ;; types + (,(concat "\\_[[:space:]]+" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types + (,(concat "[^[:word:][:multibyte:]]\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices + (,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face) + (,(concat "\\_\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; map value type + (,(concat "\\_\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map key type + (,(concat "\\_[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; channel type + (,(concat "\\_<\\(?:new\\|make\\)\\_>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; new/make type + ;; TODO do we actually need this one or isn't it just a function call? + (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type conversion + ;; Like the original go-mode this also marks compound literal + ;; fields. There, it was marked as to fix, but I grew quite + ;; accustomed to it, so it'll stay for now. + (,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields + (,(concat "\\_<\\(goto\\|break\\|continue\\)\\_>[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue + +(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." + (let* ((cur-line (line-number-at-pos)) + (val (gethash cur-line go-dangling-cache 'nope))) + (if (or (go--buffer-narrowed-p) (equal val 'nope)) + (save-excursion + (beginning-of-line) + (go--backward-irrelevant t) + (setq val (looking-back go-dangling-operators-regexp + (- (point) go--max-dangling-operator-length))) + (if (not (go--buffer-narrowed-p)) + (puthash cur-line val go-dangling-cache)))) + val)) + +(defun go--at-function-definition () + "Return non-nil if point is on the opening curly brace of a +function definition. + +We do this by first calling (beginning-of-defun), which will take +us to the start of *some* function. We then look for the opening +curly brace of that function and compare its position against the +curly brace we are checking. If they match, we return non-nil." + (if (= (char-after) ?\{) + (save-excursion + (let ((old-point (point)) + start-nesting) + (beginning-of-defun) + (when (looking-at "func ") + (setq start-nesting (go-paren-level)) + (skip-chars-forward "^{") + (while (> (go-paren-level) start-nesting) + (forward-char) + (skip-chars-forward "^{") 0) + (if (and (= (go-paren-level) start-nesting) (= old-point (point))) + t)))))) + +(defun go--indentation-for-opening-parenthesis () + "Return the semantic indentation for the current opening parenthesis. + +If point is on an opening curly brace and said curly brace +belongs to a function declaration, the indentation of the func +keyword will be returned. Otherwise the indentation of the +current line will be returned." + (save-excursion + (if (go--at-function-definition) + (progn + (beginning-of-defun) + (current-indentation)) + (current-indentation)))) + +(defun go-indentation-at-point () + (save-excursion + (let (start-nesting) + (back-to-indentation) + (setq start-nesting (go-paren-level)) + + (cond + ((go-in-string-p) + (current-indentation)) + ((looking-at "[])}]") + (go-goto-opening-parenthesis) + (if (go-previous-line-has-dangling-op-p) + (- (current-indentation) tab-width) + (go--indentation-for-opening-parenthesis))) + ((progn (go--backward-irrelevant t) + (looking-back go-dangling-operators-regexp + (- (point) go--max-dangling-operator-length))) + ;; only one nesting for all dangling operators in one operation + (if (go-previous-line-has-dangling-op-p) + (current-indentation) + (+ (current-indentation) tab-width))) + ((zerop (go-paren-level)) + 0) + ((progn (go-goto-opening-parenthesis) (< (go-paren-level) start-nesting)) + (if (go-previous-line-has-dangling-op-p) + (current-indentation) + (+ (go--indentation-for-opening-parenthesis) tab-width))) + (t + (current-indentation)))))) + +(defun go-mode-indent-line () + (interactive) + (let (indent + shift-amt + (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)) + (if (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|case .+:\\|default:")) + (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)))) + +(defun go--find-enclosing-parentheses (position) + "Return points of outermost '(' and ')' surrounding POSITION if +such parentheses exist. + +If outermost '(' exists but ')' does not, it returns the next blank +line or end-of-buffer position instead of the position of the closing +parenthesis. + +If the starting parenthesis is not found, it returns (POSITION +POSITION)." + (save-excursion + (let (beg end) + (goto-char position) + (while (> (go-paren-level) 0) + (re-search-backward "[(\\[{]" nil t) + (when (looking-at "(") + (setq beg (point)))) + (if (null beg) + (list position position) + (goto-char position) + (while (and (> (go-paren-level) 0) + (search-forward ")" nil t))) + (when (> (go-paren-level) 0) + (unless (re-search-forward "^[[:space:]]*$" nil t) + (goto-char (point-max)))) + (list beg (point)))))) + +(defun go--search-next-comma (end) + "Search forward from point for a comma whose nesting level is +the same as point. If it reaches the end of line or a closing +parenthesis before a comma, it stops at it." + (let ((orig-level (go-paren-level))) + (while (and (< (point) end) + (or (looking-at "[^,)\n]") + (> (go-paren-level) orig-level))) + (forward-char)) + (when (and (looking-at ",") + (< (point) (1- end))) + (forward-char)))) + +(defun go--looking-at-keyword () + (and (looking-at (concat "\\(" go-identifier-regexp "\\)")) + (member (match-string 1) go-mode-keywords))) + +(defun go--match-func (end) + "Search for identifiers used as type names from a function +parameter list, and set the identifier positions as the results +of last search. Return t if search succeeded." + (when (re-search-forward "\\_" end t) + (let ((regions (go--match-func-type-names end))) + (if (null regions) + ;; Nothing to highlight. This can happen if the current func + ;; is "func()". Try next one. + (go--match-func end) + ;; There are something to highlight. Set those positions as + ;; last search results. + (setq regions (go--filter-match-data regions end)) + (when regions + (set-match-data (go--make-match-data regions)) + t))))) + +(defun go--match-func-type-names (end) + (cond + ;; Function declaration (e.g. "func foo(") + ((looking-at (concat "[[:space:]\n]*" go-identifier-regexp "[[:space:]\n]*(")) + (goto-char (match-end 0)) + (nconc (go--match-parameter-list end) + (go--match-function-result end))) + ;; Method declaration, function literal, or function type + ((looking-at "[[:space:]]*(") + (goto-char (match-end 0)) + (let ((regions (go--match-parameter-list end))) + ;; Method declaration (e.g. "func (x y) foo(") + (when (looking-at (concat "[[:space:]]*" go-identifier-regexp "[[:space:]\n]*(")) + (goto-char (match-end 0)) + (setq regions (nconc regions (go--match-parameter-list end)))) + (nconc regions (go--match-function-result end)))))) + +(defun go--parameter-list-type (end) + "Return `present' if the parameter list has names, or `absent' if not. +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)))) + +(defconst go--opt-dotdotdot-regexp "\\(?:\\.\\.\\.\\)?") +(defconst go--parameter-type-regexp + (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(" go-type-name-no-prefix-regexp "\\)[[:space:]\n]*\\([,)]\\|\\'\\)")) +(defconst go--func-type-in-parameter-list-regexp + (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(\\_" "\\)")) + +(defun go--match-parameters-common (identifier-regexp end) + (let ((acc ()) + (start -1)) + (while (progn (skip-chars-forward "[:space:]\n" end) + (and (not (looking-at "\\(?:)\\|\\'\\)")) + (< start (point)) + (<= (point) end))) + (setq start (point)) + (cond + ((looking-at (concat identifier-regexp go--parameter-type-regexp)) + (setq acc (nconc acc (list (match-beginning 1) (match-end 1)))) + (goto-char (match-beginning 2))) + ((looking-at (concat identifier-regexp go--func-type-in-parameter-list-regexp)) + (goto-char (match-beginning 1)) + (setq acc (nconc acc (go--match-func-type-names end))) + (go--search-next-comma end)) + (t + (go--search-next-comma end)))) + (when (and (looking-at ")") + (< (point) end)) + (forward-char)) + acc)) + +(defun go--match-parameters-with-identifier-list (end) + (go--match-parameters-common + (concat go-identifier-regexp "[[:space:]\n]+") + end)) + +(defun go--match-parameters-without-identifier-list (end) + (go--match-parameters-common "" end)) + +(defun go--filter-match-data (regions end) + "Remove points from REGIONS if they are beyond END. +REGIONS are a list whose size is multiple of 2. Element 2n is beginning of a +region and 2n+1 is end of it. + +This function is used to make sure we don't override end point +that `font-lock-mode' gave to us." + (when regions + (let* ((vec (vconcat regions)) + (i 0) + (len (length vec))) + (while (and (< i len) + (<= (nth i regions) end) + (<= (nth (1+ i) regions) end)) + (setq i (+ i 2))) + (cond ((= i len) + regions) + ((zerop i) + nil) + (t + (butlast regions (- (length regions) i))))))) + +(defun go--make-match-data (regions) + (let ((deficit (- (* 2 go--font-lock-func-param-num-groups) + (length regions)))) + (when (> deficit 0) + (let ((last (car (last regions)))) + (setq regions (nconc regions (make-list deficit last)))))) + `(,(car regions) ,@(last regions) ,@regions)) + +(defun go--match-parameter-list (end) + "Return a list of identifier positions that are used as type +names in a function parameter list, assuming point is at the +beginning of a parameter list. Return nil if the text after +point does not look like a parameter list. + +Set point to end of closing parenthesis on success. + +In Go, the names must either all be present or all be absent +within a list of parameters. + +Parsing a parameter list is a little bit complicated because we +have to scan through the parameter list to determine whether or +not the list has names. Until a type name is found or reaching +end of a parameter list, we are not sure which form the parameter +list is. + +For example, X and Y are type names in a parameter list \"(X, +Y)\" but are parameter names in \"(X, Y int)\". We cannot say if +X is a type name until we see int after Y. + +Note that even \"(int, float T)\" is a valid parameter +list. Builtin type names are not reserved words. In this example, +int and float are parameter names and only T is a type name. + +In this function, we first scan the parameter list to see if the +list has names, and then handle it accordingly." + (let ((name (go--parameter-list-type end))) + (cond ((eq name 'present) + (go--match-parameters-with-identifier-list end)) + ((eq name 'absent) + (go--match-parameters-without-identifier-list end)) + (t nil)))) + +(defun go--match-function-result (end) + "Return a list of identifier positions that are used as type +names in a function result, assuming point is at the beginning of +a result. + +Function result is a unparenthesized type or a parameter list." + (cond ((and (looking-at (concat "[[:space:]*]*\\(" go-type-name-no-prefix-regexp "\\)")) + (not (member (match-string 1) go-mode-keywords))) + (list (match-beginning 1) (match-end 1))) + ((looking-at "[[:space:]]*(") + (goto-char (match-end 0)) + (go--match-parameter-list end)) + (t nil))) + +(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))) + +;;;###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 + (set (make-local-variable 'font-lock-defaults) + '(go--build-font-lock-keywords)) + + ;; 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) + + (if (boundp 'electric-indent-chars) + (set (make-local-variable 'electric-indent-chars) '(?\n ?} ?\)))) + + (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, 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)) + (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"))))))))) + +(defun gofmt--is-goimports-p () + (string-equal (file-name-base gofmt-command) "goimports")) + +(defun gofmt () + "Format the current buffer according to the gofmt tool." + (interactive) + (let ((tmpfile (make-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-truename buffer-file-name))))) + (setq our-gofmt-args (append our-gofmt-args + gofmt-args + (list "-w" 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 #'call-process gofmt-command nil errbuf nil our-gofmt-args)) + (progn + (if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile)) + (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 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--get-buffer (query) + "Get an empty buffer for a godoc QUERY." + (let* ((buffer-name (concat "*godoc " 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." + (interactive "r") + (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 (file-directory-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. +It looks for archive files in /pkg/." + (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:]]+\\): 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-off)) + (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-on))))) + +(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" + (number-to-string (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 "\\