├── .gitignore ├── .travis.yml ├── Cask ├── Changes ├── Makefile ├── README.md ├── go-add-tags.el ├── image └── go-add-tags.gif └── test └── test.el /.gitignore: -------------------------------------------------------------------------------- 1 | /.cask/ 2 | *.elc 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: false 3 | before_install: 4 | - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh 5 | - evm install $EVM_EMACS --use --skip 6 | - cask 7 | env: 8 | - EVM_EMACS=emacs-24.3-travis 9 | - EVM_EMACS=emacs-24.5-travis 10 | - EVM_EMACS=emacs-25.1-travis 11 | script: 12 | - emacs --version 13 | - make test 14 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "go-add-tags.el") 5 | 6 | (development 7 | (depends-on "go-mode")) 8 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for go-add-tags.el 2 | 3 | Version 0.04 2016/11/23 syohex 4 | - Drop Emacs 24.1 and 24.2 support 5 | 6 | Version 0.03 2016/10/06 syohex 7 | - Support nested struct(#5) 8 | - Implement replacing tag feature(#3) 9 | 10 | Version 0.02 2016/09/09 syohex 11 | - Fix dependency packages 12 | 13 | Version 0.01 2016/09/09 syohex 14 | - init version 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test test-function 2 | 3 | EMACS ?= emacs 4 | CASK ?= cask 5 | 6 | LOADPATH = -L . 7 | 8 | ELPA_DIR = $(shell EMACS=$(EMACS) $(CASK) package-directory) 9 | 10 | test: elpa 11 | $(CASK) exec $(EMACS) -Q -batch $(LOADPATH) \ 12 | -l test/test.el \ 13 | -f ert-run-tests-batch-and-exit 14 | 15 | elpa: $(ELPA_DIR) 16 | $(ELPA_DIR): Cask 17 | $(CASK) install 18 | touch $@ 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-add-tags.el [![travis badge][travis-badge]][travis-link] [![melpa badge][melpa-badge]][melpa-link] [![melpa stable badge][melpa-stable-badge]][melpa-stable-link] 2 | 3 | Add field tags for struct fields. This package is inspired by `GoAddTags` of [vim-go](https://github.com/fatih/vim-go). 4 | 5 | ## Screencast 6 | 7 | ![Screencast of go-add-tags. Insert tags for struct fields with M-x go-add-tags](image/go-add-tags.gif) 8 | 9 | ## Installation 10 | 11 | `go-add-tags` is available on [MELPA](https://melpa.org/) and [MELPA stable](https://stable.melpa.org/) 12 | 13 | You can install `go-add-tags` with the following command. 14 | 15 | M-x package-install [RET] go-add-tags [RET] 16 | 17 | ## Interfaces 18 | 19 | ##### `go-add-tags` 20 | 21 | Insert tag at current line. You can input multiple tags at once by comma, like `json,yaml`. 22 | If region is enabled, then tags are inserted in lines in region. And `current-prefix-key` is specified, 23 | then you can choose field style function. 24 | 25 | ## Customization 26 | 27 | ##### `go-add-tags-style`(Default: 'snake-case) 28 | 29 | How to convert field in tag from field name. 30 | 31 | - `snake-case` 32 | - `lower-camel-case` 33 | - `upper-camel-case` 34 | - `original` 35 | 36 | ## Sample Configuration 37 | 38 | ``` emacs-lisp 39 | (custom-set-variables 40 | '(go-add-tags-style 'lower-camel-case)) 41 | 42 | (with-eval-after-load 'go-mode 43 | (define-key go-mode-map (kbd "C-c t") #'go-add-tags)) 44 | ``` 45 | 46 | [travis-badge]: https://travis-ci.org/syohex/emacs-go-add-tags.svg 47 | [travis-link]: https://travis-ci.org/syohex/emacs-go-add-tags 48 | [melpa-link]: https://melpa.org/#/go-add-tags 49 | [melpa-stable-link]: https://stable.melpa.org/#/go-add-tags 50 | [melpa-badge]: https://melpa.org/packages/go-add-tags-badge.svg 51 | [melpa-stable-badge]: https://stable.melpa.org/packages/go-add-tags-badge.svg 52 | -------------------------------------------------------------------------------- /go-add-tags.el: -------------------------------------------------------------------------------- 1 | ;;; go-add-tags.el --- Add field tags for struct fields -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | ;; URL: https://github.com/syohex/emacs-go-add-tags 7 | ;; Version: 0.04 8 | ;; Package-Requires: ((emacs "24.3") (s "1.11.0")) 9 | 10 | ;; This program is free software; you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation, either version 3 of the License, or 13 | ;; (at your option) any later version. 14 | 15 | ;; This program is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | ;;; Commentary: 24 | 25 | ;; Add field tags (such as json, yaml, toml, datastore and 26 | ;; mapstructure) for struct fields. This package is inspired 27 | ;; by vim-go's GoAddTags command 28 | 29 | ;;; Code: 30 | 31 | (require 's) 32 | (require 'cl-lib) 33 | 34 | (defgroup go-add-tags nil 35 | "Add field tag for struct fields." 36 | :group 'go) 37 | 38 | (defcustom go-add-tags-style 'snake-case 39 | "How to convert field in tag from field name." 40 | :type '(choice (const :tag "snake_case" snake-case) 41 | (const :tag "camelCase" lower-camel-case) 42 | (const :tag "UpperCamelCase" upper-camel-case) 43 | (const :tag "Use original field name" original))) 44 | 45 | (defcustom go-add-tags-fields-tags 46 | '("json" "yaml" "toml" "datastore" "mapstructure") 47 | "List of field tags offered as completion candidates." 48 | :type '(repeat (symbol :tag "Field"))) 49 | 50 | (defvar go-add-tags--style-functions 51 | '((snake-case . s-snake-case) 52 | (camel-case . s-lower-camel-case) 53 | (upper-camel-case . s-upper-camel-case))) 54 | 55 | (defun go-add-tags--inside-struct-p (begin) 56 | (save-excursion 57 | (goto-char begin) 58 | (ignore-errors 59 | (backward-up-list)) 60 | (looking-back "struct\\s-*" (line-beginning-position)))) 61 | 62 | (defun go-add-tags--tag-string (tags field) 63 | (mapconcat (lambda (tag) 64 | (format "%s:\"%s\"" tag field)) 65 | tags 66 | " ")) 67 | 68 | (defun go-add-tags--tag-exist-p () 69 | (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) 70 | (string-match-p "`[^`]+`" line))) 71 | 72 | (defun go-add-tags--insert-tag (tags field) 73 | (dolist (tag tags) 74 | (save-excursion 75 | (let ((re (concat tag ":\"[^\"]+\"")) 76 | (tag-field (concat tag ":" "\"" field "\""))) 77 | (if (re-search-forward re (line-end-position) t) 78 | (replace-match tag-field) 79 | (search-forward "`" (line-end-position) t 2) 80 | (backward-char 1) 81 | (insert " " tag-field)))))) 82 | 83 | (defun go-add-tags--overwrite-or-insert-tag (tags field conv-fn) 84 | (let* ((prop (funcall conv-fn field)) 85 | (exist-p (go-add-tags--tag-exist-p))) 86 | (if (not exist-p) 87 | (insert (format " `%s`" (go-add-tags--tag-string tags prop))) 88 | (go-add-tags--insert-tag tags prop)))) 89 | 90 | (defun go-add-tags--struct-name () 91 | (save-excursion 92 | (when (ignore-errors (backward-sexp 1) t) 93 | (back-to-indentation) 94 | (when (looking-at "\\(\\S-+\\)\\s-+\\(?:\\[\\]\\)?struct\\s-*") 95 | (match-string-no-properties 1))))) 96 | 97 | (defun go-add-tags--insert-tags (tags begin end conv-fn) 98 | (save-excursion 99 | (let ((end-marker (make-marker))) 100 | (set-marker end-marker end) 101 | (goto-char begin) 102 | (goto-char (line-beginning-position)) 103 | (while (and (<= (point) end-marker) (not (eobp))) 104 | (let ((bound (min end-marker (line-end-position)))) 105 | (cond ((re-search-forward "^\\s-*\\(\\S-+\\)\\s-+\\(\\S-+\\)" bound t) 106 | (let ((field (match-string-no-properties 1)) 107 | (type-end (match-end 2)) 108 | (line (buffer-substring-no-properties 109 | (line-beginning-position) (line-end-position)))) 110 | (unless (string-match-p "struct\\s-*+{" line) 111 | (goto-char (min bound type-end)) 112 | (go-add-tags--overwrite-or-insert-tag tags field conv-fn)))) 113 | ((re-search-forward "^\\s-*}" bound t) 114 | (unless (zerop (current-indentation)) 115 | (let ((field (go-add-tags--struct-name))) 116 | (when field 117 | (go-add-tags--overwrite-or-insert-tag tags field conv-fn))))))) 118 | (forward-line 1))))) 119 | 120 | (defun go-add-tags--style-candidates (field) 121 | (cl-loop for (_ . func) in go-add-tags--style-functions 122 | collect (cons (funcall func field) func) into candidates 123 | finally return (append candidates (list (cons "Original" #'identity))))) 124 | 125 | (defun go-add-tags--prompt (candidates) 126 | (cl-loop for cand in candidates 127 | for i = 1 then (1+ i) 128 | collect (format "[%d] %s" i cand) into parts 129 | finally 130 | return (mapconcat #'identity parts " "))) 131 | 132 | (defun go-add-tags--read-style-function () 133 | (let* ((candidates (go-add-tags--style-candidates "FieldName")) 134 | (len (length candidates)) 135 | (prompt (go-add-tags--prompt (mapcar #'car candidates))) 136 | ret finish) 137 | (while (not finish) 138 | (let* ((input (read-char prompt)) 139 | (num (- input ?0))) 140 | (when (and (<= 1 num) (<= num len)) 141 | (setq finish t) 142 | (setq ret (cdr (nth (1- num) candidates)))))) 143 | ret)) 144 | 145 | ;;;###autoload 146 | (defun go-add-tags (tags begin end conv-fn) 147 | "Add field tags for struct fields." 148 | (interactive 149 | (list 150 | (completing-read-multiple "Tags: " go-add-tags-fields-tags) 151 | (or (and (use-region-p) (region-beginning)) (line-beginning-position)) 152 | (or (and (use-region-p) (region-end)) (line-end-position)) 153 | (if current-prefix-arg 154 | (go-add-tags--read-style-function) 155 | (assoc-default go-add-tags-style go-add-tags--style-functions)))) 156 | (deactivate-mark) 157 | (let ((inside-struct-p (go-add-tags--inside-struct-p begin))) 158 | (unless inside-struct-p 159 | (user-error "Here is not struct")) 160 | (save-excursion 161 | (go-add-tags--insert-tags tags begin end (or conv-fn #'identity))))) 162 | 163 | (provide 'go-add-tags) 164 | 165 | ;;; go-add-tags.el ends here 166 | -------------------------------------------------------------------------------- /image/go-add-tags.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacsorphanage/go-add-tags/93ecde9f82bc960493eaf6921d46a5adc3699ffc/image/go-add-tags.gif -------------------------------------------------------------------------------- /test/test.el: -------------------------------------------------------------------------------- 1 | ;;; test.el --- test of go-add-tags 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Code: 21 | 22 | (require 'ert) 23 | (require 'go-add-tags) 24 | 25 | (defmacro with-go-temp-buffer (code &rest body) 26 | "Insert `code'. cursor is beginning of buffer" 27 | (declare (indent 0) (debug t)) 28 | `(with-temp-buffer 29 | (insert ,code) 30 | (goto-char (point-min)) 31 | ,@body)) 32 | 33 | (ert-deftest tag-string () 34 | "internal function go-add-tags--tags-string" 35 | (let ((got (go-add-tags--tag-string '("datastore") "foo_bar")) 36 | (expected "datastore:\"foo_bar\"")) 37 | (should (string= got expected))) 38 | 39 | (let ((got (go-add-tags--tag-string '("json" "yaml") "foo_bar")) 40 | (expected "json:\"foo_bar\" yaml:\"foo_bar\"")) 41 | (should (string= got expected)))) 42 | 43 | (ert-deftest tag-exist-p () 44 | "internal function go-add-tags--tag-exist-p" 45 | (with-go-temp-buffer 46 | " 47 | type Foo struct { 48 | BarBaz string `json:\"bar_baz\"` 49 | Apple Fruit 50 | } 51 | " 52 | (search-forward "BarBaz") 53 | (should (go-add-tags--tag-exist-p)) 54 | 55 | (forward-line +1) 56 | (should-not (go-add-tags--tag-exist-p)))) 57 | 58 | (ert-deftest insert-tags () 59 | "internal function go-add-tags--insert-tags" 60 | (with-go-temp-buffer 61 | " 62 | type Foo struct { 63 | BarBaz string 64 | Apple Fruit `yaml:\"apple\"` 65 | } 66 | " 67 | (search-forward "BarBaz") 68 | (go-add-tags--insert-tags 69 | '("json") (line-beginning-position) (line-end-position) #'s-lower-camel-case) 70 | (let ((line (buffer-substring (line-beginning-position) (line-end-position)))) 71 | (should (s-contains? "`json:\"barBaz\"`" line))) 72 | 73 | (forward-line +1) 74 | (go-add-tags--insert-tags 75 | '("json") (line-beginning-position) (line-end-position) #'s-snake-case) 76 | (let ((line (buffer-substring (line-beginning-position) (line-end-position)))) 77 | (should (s-contains? "`yaml:\"apple\" json:\"apple\"`" line))))) 78 | 79 | (ert-deftest replace-tags () 80 | "internal function go-add-tags--insert-tags in replacing case" 81 | (with-go-temp-buffer 82 | " 83 | type Foo struct { 84 | BarBaz string `json:\"barbaz\"` 85 | AppleOrange Fruit `yaml:\"apple_orange\" json:\"apple_orange\"` 86 | } 87 | " 88 | (search-forward "BarBaz") 89 | (go-add-tags--insert-tags 90 | '("json") (line-beginning-position) (line-end-position) #'s-lower-camel-case) 91 | (let ((line (buffer-substring (line-beginning-position) (line-end-position)))) 92 | (should (s-contains? "`json:\"barBaz\"`" line))) 93 | 94 | (forward-line +1) 95 | (go-add-tags--insert-tags 96 | '("json" "yaml") (line-beginning-position) (line-end-position) #'s-snake-case) 97 | (let ((line (buffer-substring (line-beginning-position) (line-end-position)))) 98 | (should (s-contains? "`yaml:\"apple_orange\" json:\"apple_orange\"`" line))))) 99 | 100 | (ert-deftest nested-tag () 101 | "Insert tag for nested struct 102 | https://github.com/syohex/emacs-go-add-tags/issues/4" 103 | (with-go-temp-buffer 104 | " 105 | type APIErr struct { 106 | Message string 107 | Errors []struct { 108 | Resource string 109 | Code string 110 | } 111 | } 112 | " 113 | (let ((expected " 114 | type APIErr struct { 115 | Message string `json:\"message\"` 116 | Errors []struct { 117 | Resource string `json:\"resource\"` 118 | Code string `json:\"code\"` 119 | } `json:\"errors\"` 120 | } 121 | ")) 122 | 123 | (forward-line 1) 124 | (go-add-tags--insert-tags 125 | '("json") (point) (point-max) #'s-lower-camel-case) 126 | (should (string= (buffer-string) expected))))) 127 | 128 | ;;; test.el ends here 129 | --------------------------------------------------------------------------------