diff options
Diffstat (limited to '.config/emacs/lisp/evil-nerd-commenter')
3 files changed, 1542 insertions, 0 deletions
diff --git a/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-operator.el b/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-operator.el new file mode 100644 index 0000000..15739a1 --- /dev/null +++ b/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-operator.el @@ -0,0 +1,402 @@ +;;; evil-nerd-commenter-operator.el --- evil operator for this program -*- lexical-binding: t -*- + +;; Author: Chen Bin <chenbin.sh@gmail.com> + +;; This file is not part of GNU Emacs. + +;;; License: + +;; 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 3, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: + +;; Provides operators for evil-mode. + +;;; Code: + +(require 'evil) +(require 'evil-nerd-commenter-sdk) + +(defvar evilnc-c-style-comment-modes + '(awk-mode + c++-mode + c-mode + css-mode + dart-mode + ess-mode + go-mode + java-mode + javascript-mode + js-mode + js2-mode + perl-mode + php-mode + swift-mode + web-mode) + "Major modes using C comment syntax.") + +(defvar evilnc-current-text-object nil + "Internal variable to detect current object working on.") + +(defvar evilnc-whole-line-text-objects + '(evil-forward-paragraph + evil-backward-paragraph) + "Comment operator operates on wholes when dealing with these text objects.") + +(defvar evilnc-temporary-goal-column 0 + "Value of`temporary-goal-column' specifying right edge of rectangle yank.") + +(defadvice evil-visual-highlight-block (around evil-visual-highlight-block-hack activate) + "Show overlay over inner comment text object." + ad-do-it + (when (eq this-command 'evilnc-inner-commenter) + (dolist (overlay evil-visual-block-overlays) + (let* ((b (overlay-start overlay)) + (e (save-excursion + (goto-char (overlay-end overlay)) + (line-end-position)))) + (move-overlay overlay b e))))) + +(defadvice evil-apply-on-block (around evil-apply-on-block-around-hack activate) + "Yank correct region of inner comment text object." + (let* ((tmp-command last-command)) + ;; force `evil-apply-on-block' use our temporary-goal-column + (when (> evilnc-temporary-goal-column 0) + (setq temporary-goal-column (max temporary-goal-column + evilnc-temporary-goal-column)) + ;; Read `evil-apply-on-block'. Note `temporary-goal-column' is used + ;; if and only if `last-command' is `next-line' or `previous-line' + (setq last-command 'next-line)) + ad-do-it + ;; restore last command + (setq last-command tmp-command) + (setq evilnc-temporary-goal-column 0))) + +(defun evilnc-expand-to-whole-comment-or-line (start end) + "Expand the comment region defined by START and END. +Make sure all comment is included. +Or expand the region to contain whole lines." + + (cond + ((evilnc-pure-comment-p start) + (save-excursion + (let* ((newstart start) + (newend end)) + + ;; expand the beginning + (goto-char newstart) + (while (and (>= (1- newstart) (line-beginning-position)) (evilnc-pure-comment-p (1- newstart))) + (setq newstart (1- newstart))) + + ;; expand the end + (goto-char newend) + (while (and (<= newend (line-end-position)) (evilnc-pure-comment-p newend)) + (setq newend (1+ newend))) + + (cons newstart newend)))) + + ;; try to expand region to contain whole line if, + ;; - currently more than one line text in the region, + ;; - a specific text object is touched just before the operator + ((and (not (evilnc-sdk-inside-one-line-p start end)) + evilnc-current-text-object + (member (car evilnc-current-text-object) evilnc-whole-line-text-objects) + ;; 0.5 second + (< (- (float-time (current-time)) (cdr evilnc-current-text-object)) 0.5)) + (evilnc-sdk-expand-to-contain-whole-lines start end)) + (t + (cons start end)))) + +;; {{ know text object type to operate on +(defun evilnc-set-current-text-object (text-object) + "Set current TEXT-OBJECT." + (setq evilnc-current-text-object + (cons text-object (float-time (current-time))))) + +(defadvice evil-select-an-object (before evilnc-evil-select-an-object-hack activate) + "Figure out text object type." + (let* ((thing (car (ad-get-args 0)))) + ;; record the thing and timestamp `evil-select-an-object' is called + (evilnc-set-current-text-object thing))) + +(defadvice evil-backward-paragraph (before evilnc-evil-backward-paragraph-hack activate) + "Record current text object." + (evilnc-set-current-text-object 'evil-backward-paragraph)) + +(defadvice evil-forward-paragraph (before evilnc-evil-forward-paragraph-hack activate) + "Record current text object." + (evilnc-set-current-text-object 'evil-forward-paragraph)) +;; }} + +(evil-define-operator evilnc-comment-operator (start end type) + "Comments text from START to END with TYPE." + (interactive "<R>") + (cond + ((eq type 'block) + (let* ((newpos (evilnc-expand-to-whole-comment-or-line start end) )) + (evil-apply-on-block #'evilnc-comment-or-uncomment-region + (car newpos) + (cdr newpos) + nil))) + + ((and (eq type 'line) + (= end (point-max)) + (or (= start end) + (/= (char-before end) ?\n)) + (/= start (point-min)) + (= (char-before start) ?\n)) + (evilnc-comment-or-uncomment-region (1- start) end)) + + ((eq type 'line) + ;; comment whole line, for now + (evilnc-comment-or-uncomment-region start + (save-excursion + (goto-char (1- end)) + (line-end-position)))) + + (t + (when (and start end) + (let* ((newpos (evilnc-expand-to-whole-comment-or-line start end))) + (evilnc-comment-or-uncomment-region (car newpos) (cdr newpos)))))) + + ;; place cursor on beginning of line + (if (and (called-interactively-p 'any) (eq type 'line)) + (evil-first-non-blank))) + +(defun evilnc-comment-or-uncomment-region-then-action (start end commenter &optional action) + "Comment/uncomment between START and END using COMMENTER, then take ACTION." + (evil-with-single-undo + ;; yank original text + (evil-yank-lines start end nil 'lines) + + (when (evil-visual-state-p) + ;; `evil-paste-before' does not work in visual state. + (evil-normal-state)) + + (cond + (evilnc-original-above-comment-when-copy-and-comment + (let* ((p (point))) + (funcall commenter start end) + (goto-char start) + (when action (funcall action)) + (goto-char p))) + + (t + (goto-char end) + (when action (funcall action)) + ;; actual comment operation should happen at last + ;; or else "(start end)" is screwed up + (funcall commenter start end))))) + +(evil-define-operator evilnc-yank-and-comment-operator (start end) + "(Un)comment and yank the text from START to END." + :move-point (not evilnc-original-above-comment-when-copy-and-comment) + (interactive "<r>") + (evilnc-comment-or-uncomment-region-then-action start + end + evilnc-comment-or-uncomment-region-function)) + +(evil-define-operator evilnc-copy-and-comment-operator (start end) + "Inserts a commented copy of the text from START to END." + :move-point (not evilnc-original-above-comment-when-copy-and-comment) + (interactive "<r>") + (evilnc-comment-or-uncomment-region-then-action start + end + 'comment-region + (lambda () (evil-paste-before 1)))) + +(defun evilnc-one-line-comment-p (start end) + "Test if text between START and END is one line comment." + (save-excursion + (goto-char start) + (and (<= (line-beginning-position) start) + ;; end is the upper limit great than (line-end-position) + (<= end (1+ (line-end-position)))))) + +(defun evilnc-get-comment-bounds () + "Return bounds like (cons start end)." + (let* ((b (point)) + (e (point)) + (col 0) + rlt) + ;; decrease start position + (while (evilnc-comment-p (- b 1)) + (setq b (- b 1))) + + ;; increase end + (while (evilnc-comment-p (+ e 1)) + (setq e (+ e 1))) + + ;; we could select extra spaces at the end of comment + ;; so we need go back + (let* ((str (save-excursion + (goto-char e) + (evilnc-sdk-cur-line e))) + (empty-line-p (string-match "^[ \t]*$" str))) + (when empty-line-p + ;; empty line plus line feed + (setq e (- e (length str) 1)))) + (cond + ((>= b e) + (setq rlt nil)) + ((evilnc-one-line-comment-p b e) + ;; contract from start position + (while (not (evilnc-pure-comment-p b)) + (setq b (+ b 1))) + + ;; contract from end position + (while (not (evilnc-pure-comment-p e)) + (setq e (- e 1))) + + (if (< b e) (setq rlt (cons b e)))) + (t + ;; multi-line comment + (setq rlt (cons b e)))) + rlt)) + +(defun evilnc-adjusted-comment-end (b e) + "Adjust comment end of region between B and E." + (let* ((next-end-char (evilnc-get-char (- e 2))) + (end-char (evilnc-get-char (- e 1)))) + ;; avoid selecting CR/LF at the end of comment + (while (and (< b e) + (memq (evilnc-get-char (- e 1)) '(10 13))) + (setq e (- e 1))) + + ;; avoid selecting comment limiter + (cond + ((and (memq major-mode evilnc-c-style-comment-modes) + (= end-char ?/) + (= next-end-char ?*)) + ;; avoid selecting the ending comment limiter "*/" + (setq e (- e 2)) + (while (and (> e b) + (= (evilnc-get-char (- e 1)) ?*)) + (setq e (- e 1)))) + (t + ;; other languages we can safely use font face + (while (and (> e b) + (evilnc-comment-delimiter-p (- e 1))) + (setq e (- e 1))))) + e)) + +(defun evilnc-c-style-comment-p (pos) + "Is C style comment at POS?" + (and (memq major-mode evilnc-c-style-comment-modes) + (= (evilnc-get-char pos) ?/) + (= (memq (evilnc-get-char (1+ pos)) '(?/ ?*))))) + +(defun evilnc-comment-column-bounds (start end &optional c-style) + "From START to END find column bounds of rectangle selection. +Return (cons col-min col-max) or nil. If C-STYLE is t, +we are processing C like language." + (let* ((col-min most-positive-fixnum) + (col-max 0)) + (while (< start end) + (when (and (not (evilnc-whitespace-p start)) + (evilnc-pure-comment-p start) + (not (or (evilnc-comment-delimiter-p start) + (and c-style + (memq (evilnc-get-char start) '(?/ ?*)))))) + (let* ((col (evil-column start))) + (if (< col col-min) + (setq col-min col)) + (if (> col col-max) + (setq col-max col)))) + (setq start (1+ start))) + (if (< col-min col-max) + (cons col-min col-max)))) + +(evil-define-text-object evilnc-inner-commenter (&optional count start end type) + "An inner comment text object." + (let* ((bounds (evilnc-get-comment-bounds)) + b + e + c-style) + (cond + (bounds + (setq b (car bounds)) + (setq e (cdr bounds)) + (cond + ((setq c-style (evilnc-c-style-comment-p b)) + (while (and (< b e) + (or (evilnc-whitespace-p b) + (evilnc-line-end-p b) + (memq (evilnc-get-char b) '(?/ ?*)))) + (setq b (1+ b))) + (while (and (< b e) + (or (evilnc-whitespace-p e) + (evilnc-line-end-p e) + (memq (evilnc-get-char e) '(?/ ?*)))) + (setq e (1- e))) + (setq e (1+ e)) + (setq b (save-excursion + (goto-char b) + (forward-word 1) + (forward-word -1) + (point)))) + (t + (setq b (save-excursion + (goto-char (car bounds)) + (forward-word 1) + (forward-word -1) + (point))) + (setq e (save-excursion + (goto-char (cdr bounds)) + (goto-char (evilnc-adjusted-comment-end b (line-end-position))) + (point))))) + (cond + ((evilnc-one-line-comment-p b e) + ;; keep move e to the end of comment + (evil-range b ;; (if c-style (1+ e) e) + e)) + (t + ;; multi-line comment + (let* ((col-b (evil-column b)) + (col-bounds (evilnc-comment-column-bounds b e c-style))) + (cond + (col-bounds + (if (> col-b (car col-bounds)) + (setq b (- b (- col-b (car col-bounds))))) + (setq evilnc-temporary-goal-column (cdr col-bounds))) + (t + (setq evilnc-temporary-goal-column (evil-column e))))) + (evil-range b e 'block :expanded t)))) + (t + (error "Not inside a comment"))))) + +(evil-define-text-object evilnc-outer-commenter (&optional count start end type) + "An outer comment text object." + (let* ((bounds (evilnc-get-comment-bounds))) + (cond + (bounds + (evil-range (car bounds) (cdr bounds) 'exclusive :expanded t)) + (t + (error "Not inside a comment"))))) + +(evil-define-text-object evilnc-inner-comment (&optional count start end type) + "An inner comment text object." + (evilnc-inner-commenter count start end type)) + +(evil-define-text-object evilnc-outer-comment (&optional count start end type) + "An outer comment text object." + (evilnc-outer-commenter count start end type)) + +(provide 'evil-nerd-commenter-operator) +;;; evil-nerd-commenter-operator.el ends here + +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-sdk.el b/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-sdk.el new file mode 100644 index 0000000..60d31dc --- /dev/null +++ b/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-sdk.el @@ -0,0 +1,127 @@ +;;; evil-nerd-commenter-sdk.el --- SDK used by other files -*- lexical-binding: t -*- + +;; Author: Chen Bin + +;;; License: + +;; This file is not part of GNU Emacs. + +;; 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 3, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: +;; SDK used internally + +;;; Code: + +(require 'evil nil t) +(defvar evil-state) + +(defun evilnc--check-fonts (fonts-under-cursor fonts-list) + "Check whether FONTS-UNDER-CURSOR among FONTS-LIST." + (delq nil + (mapcar #'(lambda (f) + ;; learn this trick from flyspell + (member f fonts-list)) + fonts-under-cursor))) + +(defun evilnc-web-mode-comment-p (&optional pos) + "Check whether the code at POS is comment. +`web-mode' removes its API, so create our own." + (unless pos (setq pos (point))) + (not (null (or (eq (get-text-property pos 'tag-type) 'comment) + (eq (get-text-property pos 'block-token) 'comment) + (eq (get-text-property pos 'part-token) 'comment))))) + +(defun evilnc-fonts-at-point (pos) + "Get font faces at POS." + (let* ((fontfaces (if (> pos 0) (get-text-property pos 'face)))) + (if (listp fontfaces) fontfaces (list fontfaces)))) + +(defun evilnc-pure-comment-p (pos) + "Check character at POS is pure comment." + (or (and (eq major-mode 'web-mode) + (evilnc-web-mode-comment-p pos)) + (evilnc--check-fonts (evilnc-fonts-at-point pos) + '(font-lock-comment-face + tree-sitter-hl-face:comment + font-lock-comment-delimiter-face)))) + +(defmacro evilnc-get-char (position) + "Get character at POSITION." + `(char-after ,position)) + +(defmacro evilnc-whitespace-p (position) + "Character at POSITION is white space." + `(member (evilnc-get-char ,position) '(32 9))) + +(defmacro evilnc-line-end-p (position) + "Character at POSITION is line end." + `(member (evilnc-get-char ,position) '(10 11))) + +(defun evilnc-comment-p (pos) + "Check whether the code at POS is comment by comparing font face. +Please note the white spaces out of comment is treated as comment, +or else we can't select multiple lines comment." + (cond + ((or (< pos (point-min)) (> pos (point-max))) + nil) + ((not (evilnc-fonts-at-point pos)) + ;; character under cursor is SPACE or TAB + ;; and out of comment + (evilnc-whitespace-p pos)) + (t + (evilnc-pure-comment-p pos)))) + +(defun evilnc-comment-delimiter-p (position) + "Is character at POSITION a comment delimiter?" + (let* ((fontfaces (evilnc-fonts-at-point position))) + (and fontfaces + (evilnc--check-fonts fontfaces + '(font-lock-comment-delimiter-face))))) + +(defun evilnc-sdk-inside-one-line-p (start end) + "Test START and END is inside one line." + (and (<= (line-beginning-position) start) + (<= end (line-end-position)))) + +(defun evilnc-sdk-cur-line (&optional end) + "String from line beginning to END or line end." + (buffer-substring-no-properties (line-beginning-position) + (or end (line-end-position)))) + +(defun evilnc-sdk-expand-to-contain-whole-lines (start end) + "Expand region between START and END so the region contain whole lines. +Return new range like '(region_begin . region_end)." + (save-excursion + ;; Another work around for evil-visual-line bug: + ;; In `evil-mode', if we use hotkey V or `evil-visual-line' to select line, + ;; the (line-beginning-position) of the line which is after the last selected + ;; line is always (region-end)! Don't know why. + (when (and (> end start) + (save-excursion + (goto-char end) + (= end (line-beginning-position))) + (boundp 'evil-state) + (eq evil-state 'visual)) + (setq end (1- end))) + + (goto-char start) + (setq start (line-beginning-position)) + (goto-char end) + (setq end (line-end-position))) + (cons start end)) + +(provide 'evil-nerd-commenter-sdk) +;;; evil-nerd-commenter-sdk.el ends here diff --git a/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter.el b/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter.el new file mode 100644 index 0000000..21498c4 --- /dev/null +++ b/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter.el @@ -0,0 +1,1013 @@ +;;; evil-nerd-commenter.el --- Comment/uncomment lines efficiently. Like Nerd Commenter in Vim -*- lexical-binding: t -*- + +;; Author: Chen Bin <chenbin.sh@gmail.com> + +;; URL: http://github.com/redguardtoo/evil-nerd-commenter +;; Version: 3.6.1 +;; Package-Requires: ((emacs "26.1")) +;; Keywords: convenience evil +;; +;; This file is not part of GNU Emacs. + +;;; Credits: + +;; - Lally Oppenheimer added the support for text-object in Evil +;; - Tom Willemse provided the fix to make Emacs 24.4 work + +;;; License: + +;; 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 3 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, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; +;; This program emulates nerd-commenter.vim by Marty Grenfell. +;; +;; It helps you comment/uncomment multiple lines without selecting them. +;; +;; `M-x evilnc-default-hotkeys` assigns hotkey `M-;` to +;; `evilnc-comment-or-uncomment-lines' +;; +;; `M-x evilnc-comment-or-uncomment-lines` comment or uncomment lines. +;; +;; `M-x evilnc-quick-comment-or-uncomment-to-the-line` will comment/uncomment +;; from current line to specified line. +;; The last digit(s) of line number is parameter of the command. +;; +;; For example, `C-u 9 evilnc-quick-comment-or-uncomment-to-the-line` comments +;; code from current line to line 99 if you current line is 91. +;; +;; Though this program could be used *independently*, it's recommended to +;; us it with Evil. +;; +;; Evil makes you take advantage of power of Vi to comment lines. +;; For example, you can press key `99,ci` to comment out 99 lines. +;; +;; Setup: +;; +;; If comma is your leader key, as most Vim users do, setup is one liner, +;; (evilnc-default-hotkeys) +;; +;; Or else you can set up key bindings of below commands by yourself. +;; `evilnc-comment-or-uncomment-lines' +;; `evilnc-quick-comment-or-uncomment-to-the-line' +;; `evilnc-comment-and-kill-ring-save' +;; `evilnc-copy-and-comment-lines' +;; `evilnc-comment-or-uncomment-paragraphs' +;; `evilnc-comment-box' +;; `evilnc-toggle-invert-comment-line-by-line' +;; `evilnc-copy-and-comment-operator' +;; `evilnc-comment-operator' +;; `evilnc-comment-or-uncomment-html-tag' +;; `evilnc-comment-or-uncomment-html-paragraphs' +;; +;; Set up `evilnc-original-above-comment-when-copy-and-comment' +;; to decide which style to use in `evilnc-copy-and-comment-lines' +;; and `evilnc-copy-and-comment-operator', +;; - Place the commented out text above original text +;; - Or place the original text above commented out text +;; +;; `evilnc-yank-and-comment-operator' (un)comment&yank text in one shot. +;; +;; Comment text object "c" is defined. It can have multi-lines. +;; Press "vac" to select outer object (comment with limiters). +;; Press "vic" to select inner object (comment without limiter). +;; You can assign other key instead of "c" to the text object by +;; customizing `evilnc-comment-text-object'. Either, +;; (setq evilnc-comment-text-object "c") +;; (evilnc-default-hotkeys) +;; +;; Or, +;; (setq evilnc-comment-text-object "a") +;; (define-key evil-inner-text-objects-map evilnc-comment-text-object 'evilnc-inner-commenter) +;; (define-key evil-outer-text-objects-map evilnc-comment-text-object 'evilnc-outer-commenter) +;; +;; You can list of comments in current buffer through using imenu. +;; by setup `imenu-create-index-function' to `evilnc-imenu-create-index-function', +;; +;; (defun counsel-imenu-comments () +;; (interactive) +;; (let* ((imenu-create-index-function 'evilnc-imenu-create-index-function)) +;; (unless (featurep 'counsel) (require 'counsel)) +;; (counsel-imenu))) +;; +;; For certain major modes, you need manual setup to override its original +;; keybindings, +;; +;; (defun matlab-mode-hook-config () +;; (local-set-key (kbd "M-;") 'evilnc-comment-or-uncomment-lines)) +;; (add-hook 'matlab-mode-hook 'matlab-mode-hook-config) +;; +;; Most commands call `evilnc-comment-or-uncomment-region-function'. +;; You can modify this variable to customize the comment style, +;; +;; (with-eval-after-load 'evil-nerd-commenter +;; (defun my-comment-or-uncomment-region (start end) +;; (let* ((comment-start "aaa") +;; (comment-end "bbb")) +;; (evilnc-comment-or-uncomment-region-internal start end))) +;; (setq evilnc-comment-or-uncomment-region-function +;; 'my-comment-or-uncomment-region)) +;; +;; See "Options Controlling Comments" in Emacs manual for comment options. +;; +;; See https://github.com/redguardtoo/evil-nerd-commenter for detail. +;; +;;; Code: + +(require 'subr-x) ; required by `string-trim' +(require 'sgml-mode) +(require 'newcomment) +(require 'evil-nerd-commenter-sdk) + +(autoload 'count-lines "simple") + +(defvar evilnc-original-above-comment-when-copy-and-comment nil + "Keep the original text above the commented copy, when using either: +`evilnc-copy-and-comment-lines' or `evilnc-copy-and-comment-operator'.") + +(defvar evilnc-invert-comment-line-by-line nil + "If t then invert region comment status line by line. +Please note it has NOT effect on evil text object!") + +(defvar evilnc-comment-or-uncomment-region-function + 'evilnc-comment-or-uncomment-region-internal + "Comment or uncomment region.") + +(defvar evilnc-cpp-like-comment-syntax-modes + '(java-mode + javascript-mode + js-mode + js2-mode + js3-mode + rjsx-mode + js2-jsx-mode + rust-mode + c++-mode + c-mode + objc-mode) + "Major modes which has C++ like comment syntax.") + +(defvar evilnc-comment-text-object "c" + "The comment object. +`vic` to select inner object. +`vac` to select outer object.") + +(defvar evilnc-use-comment-object-setup t + "Use evil text object setup when calling `evilnc-default-hotkeys'.") + +(defvar evilnc-min-comment-length-for-imenu 8 + "Minimum length of comment to display in imenu.") + +(defvar evilnc-html-comment-delimiters + '((evilnc-html-jsx-p "{/* " " */}") + ("js-mode" "{/* " " */}") + (("web-mode" "html-mode") "<!-- " " -->")) + "List of html tag comment rules. +The 1st item of each rule is the major mode(s) to match current `major-mode'. +Current `major-mode' could equal or derive from the listed major mode(s). +The 2nd and 3rd item is the comment start and comment end.") + +(defun evilnc-html-jsx-p () + "Test if current file is jsx." + (and buffer-file-name + (string-match-p "\.[tj]sx?$" buffer-file-name))) + +(defun evilnc-html-match-comment-delimiters-p (target-mode) + "Use `major-mode' to match TARGET-MODE which could be: +One major mode. +List of major modes. +A function to return t or nil." + (let* (rlt) + (cond + ((functionp target-mode) + (setq rlt (funcall target-mode))) + ((stringp target-mode) + ;; convert name of `major-mode' to symbol + (when (or (eq major-mode (intern target-mode)) + (derived-mode-p (intern target-mode))) + (setq rlt t))) + ((listp target-mode) + (while (and (not rlt) target-mode) + (unless (setq rlt (evilnc-html-match-comment-delimiters-p (nth 0 target-mode))) + ;; continue looping + (setq target-mode (cdr target-mode)))))) + rlt)) + +(defun evilnc-html-find-comment-delimiter () + "Found comment delimiter from `evilnc-html-find-comment-delimiter'." + (let* (found + (delimiters evilnc-html-comment-delimiters) + rule) + (while (and (not found) delimiters) + (setq rule (car delimiters)) + (cond + ((evilnc-html-match-comment-delimiters-p (nth 0 rule)) + (setq found rule)) + (t + ;; keep looping + (setq delimiters (cdr delimiters))))) + found)) + +(defun evilnc-html-comment-start () + "Return start of html comment." + (let* ((s (evilnc-html-find-comment-delimiter))) + (if s (nth 1 s) "<!-- "))) + +(defun evilnc-html-comment-end () + "Return end of html comment." + (let* ((s (evilnc-html-find-comment-delimiter))) + (if s (nth 2 s) " -->"))) + +(defun evilnc--goto-line (line-num) + "Shamelessly copied from `goto-line'. Goto line with LINE-NUM." + (save-restriction + (widen) + (goto-char (point-min)) + (if (eq selective-display t) + (re-search-forward "[\n\C-m]" nil 'end (1- line-num)) + (forward-line (1- line-num))))) + +(defun evilnc--fix-buggy-major-modes () + "Fix major modes whose comment regex is buggy. +See http://lists.gnu.org/archive/html/bug-gnu-emacs/2013-03/msg00891.html." + (cond + ((eq major-mode 'autoconf-mode) + ;; since comment-use-syntax is nil in autoconf.el, the comment-start-skip need + ;; make sure its first parenthesized expression match the string exactly before + ;; the "dnl", check the comment-start-skip in lisp-mode for sample. + ;; See code in (defun comment-search-forward) from emacs 24.2: + ;; (if (not comment-use-syntax) + ;; (if (re-search-forward comment-start-skip limit noerror) + ;; (or (match-end 1) (match-beginning 0))) + ;; (do-something)) + ;; My regex makes sure (match-end 1) return the position of comment starter + (when (and (boundp 'comment-use-syntax) (not comment-use-syntax)) + ;; Maybe autoconf.el will (setq comment-use-syntax t) in the future? + (setq comment-start-skip "^\\(\\s*\\)\\(dnl\\|#\\) +"))) + ((eq major-mode 'haml-mode) + (setq comment-use-syntax nil) + (setq comment-start "-# ") + (setq comment-start-skip "-##*[ \t]*")))) + +(defun evilnc--forward-line (num) + "Move NUM source or screen lines forward, depending on visual-line settings." + (cond + ((or (and visual-line-mode + (or (not (bound-and-true-p evil-mode)) + (bound-and-true-p evil-respect-visual-line-mode))) + (and (boundp 'display-line-numbers-type) + (eq display-line-numbers-type 'visual))) + (vertical-motion num)) + (t + (forward-line num)))) + +(defun evilnc--operation-on-lines-or-region (fn &optional num) + "Apply FN on NUM lines or selected region." + (cond + ;; NO region is selected + ((not (region-active-p)) + (let* ((b (line-beginning-position)) e) + (save-excursion + (evilnc--forward-line (- num 1)) + (setq e (line-end-position))) + (funcall fn b e))) + + ((evilnc-sdk-inside-one-line-p (region-beginning) (region-end)) + (cond + ;; current comment syntax is NOT fit to comment out a region. + ;; So we also need hack the `comment-start' and `comment-end'. + ((and (string= "" comment-end) + (member major-mode evilnc-cpp-like-comment-syntax-modes)) + (let* ((comment-start-old comment-start) + (comment-end-old comment-end) + (comment-start-skip-old comment-start-skip) + (comment-end-skip-old comment-end-skip)) + + ;; use C comment syntax temporarily + (setq comment-start "/* ") + (setq comment-end " */") + (setq comment-start-skip "\\(//+\\|/\\*+\\)\\s *") + (setq comment-end-skip "[ ]*\\(\\s>\\|\\*+/\\)") + + (funcall fn (region-beginning) (region-end)) + + ;; Restore the original comment syntax + (setq comment-start comment-start-old) + (setq comment-end comment-end-old) + (setq comment-start-skip comment-start-skip-old) + (setq comment-end-skip comment-end-skip-old))) + ;; just comment out the region + (t (funcall fn (region-beginning) (region-end))))) + + ;; Select more than one line + (t + ;; selected region spans MORE than one line + (let* ((range (evilnc-sdk-expand-to-contain-whole-lines (region-beginning) + (region-end))) + (b (car range)) + (e (cdr range))) + (save-excursion + (funcall fn b e)))))) + +(defun evilnc--get-one-paragraph-region () + "Select a paragraph which has NO empty line." + (let* ((b (save-excursion + (cond + ((re-search-backward "^[ \t]*$" nil t) + (forward-line) + (line-beginning-position)) + (t + 1)))) + (e (save-excursion + (cond + ((re-search-forward "^[ \t]*$" nil t) + (forward-line -1) + (line-end-position)) + (t + (point-max)))))) + (list b e))) + +(defun evilnc--invert-comment (start end) + "Scan the region from START to END line by line, invert its comment status." + (let* (done b e) + (save-excursion + (goto-char end) + (while (not done) + (setq b (line-beginning-position)) + (setq e (line-end-position)) + (funcall (if (comment-only-p b e) + 'uncomment-region 'comment-region) + b e) + + (forward-line -1) + (if (or (= (line-beginning-position) b) + (< (line-end-position) start)) + (setq done t)))))) + +(defvar org-src-lang-modes) +(declare-function org-show-subtree "org") +(declare-function outline-up-heading "outline") +(declare-function org-element-property "org-element") + +(defun evilnc--org-src-block-info () + "Return src-block info in org. It's like (start end language)." + (cond + ;; Emacs 24.4+ + ((fboundp 'org-edit-src-find-region-and-lang) + (let* ((info (org-edit-src-find-region-and-lang))) + (list (nth 0 info) + (1+ (nth 1 info)) + (nth 2 info)))) + + ;; Emacs 26.1 + ((fboundp 'org-element-at-point) + (let* ((elem (org-element-at-point)) + (b (org-element-property :begin elem)) + (e (org-element-property :end elem)) + (lang (org-element-property :language elem)) + (str (buffer-substring-no-properties b e)) + (case-fold-search t)) + (when (string-match ".\+BEGIN_SRC .*$" str) + (setq b (+ b (length (match-string 0 str)) 1))) + (save-excursion + (goto-char b) + (search-forward "#+END_SRC" e t) + ;; - 1 to remove line feed + (setq e (- (point) (length "#+END_SRC")))) + ;; Now Emacs 24.4 and Emacs 26.1 return same result + (list b e lang))))) + +(defun evilnc--org-lang-major-mode (info) + "Get `major-mode' from INFO of org source block." + (let* ((lang (nth 2 info))) + (cond + ((setq lang (or (cdr (assoc lang org-src-lang-modes)) + lang)) + (intern (concat (if (symbolp lang) (symbol-name lang) lang) + "-mode"))) + (t + nil)))) + +(defun evilnc--working-on-region (start end fn) + "Region from START to END is applied with operation FN. +Code snippets embedded in Org-mode is identified and right `major-mode' is used." + (let* ((old-pos (point)) + (info (if (eq major-mode 'org-mode) (evilnc--org-src-block-info))) + (lang-f (and info (evilnc--org-lang-major-mode info)))) + + ;; turn on 3rd party language's major-mode temporarily + (cond + (lang-f + (let* ((src-beg (nth 0 info)) + (src-end (nth 1 info)) + (comment-beg-in-buf (1+ (- start src-beg))) + (comment-end-in-buf (1+ (- end src-beg))) + (new-pos (1+ (- old-pos src-beg))) + (old-code (buffer-substring-no-properties src-beg src-end)) + new-code) + (with-temp-buffer + (goto-char (point-min)) + (insert old-code) + (funcall lang-f) + (goto-char new-pos) + + (cond + (evilnc-invert-comment-line-by-line + (evilnc--invert-comment comment-beg-in-buf comment-end-in-buf)) + (t + (funcall fn comment-beg-in-buf comment-end-in-buf))) + + (setq new-code (buffer-substring-no-properties (point-min) (point-max)))) + + (delete-region src-beg src-end) + (insert new-code) + ;; go back to original position when comment inside org src-block + (goto-char old-pos))) + (t + (cond + (evilnc-invert-comment-line-by-line + (evilnc--invert-comment start end)) + (t + (funcall fn start end))))))) + +(declare-function web-mode-comment-or-uncomment "ext:web-mode") +(declare-function web-mode-uncomment-erb-block "ext:web-mode") +(declare-function web-mode-comment-erb-block "ext:web-mode") +(defvar web-mode-engine) + +(defun evilnc--warn-on-web-mode (is-comment) + "Check certain part of html code IS-COMMENT." + (let* ((comment-operation (concat "web-mode-" + (if is-comment "comment-" "uncomment-") + web-mode-engine + "-block"))) + (unless (intern-soft comment-operation) + (message "defun %s NOT implemented in web-mode! Fix it or report to its maintainer." + comment-operation)) + is-comment)) + +;;;###autoload +(defun evilnc-comment-or-uncomment-region-internal (start end) + "Comment or uncomment region from START to END." + (cond + ((eq major-mode 'web-mode) + ;; elixir is not supported in web-mode for now + (unless (fboundp 'web-mode-comment-elixir-block) + (defun web-mode-comment-elixir-block (pos) + (funcall 'web-mode-comment-erb-block pos)) + (defun web-mode-uncomment-elixir-block (pos) + (funcall 'web-mode-uncomment-erb-block pos))) + (web-mode-comment-or-uncomment-region start end)) + (t + (evilnc--working-on-region start end 'comment-or-uncomment-region)))) + +;;;###autoload +(defun evilnc-comment-or-uncomment-region (start end) + "Comment or uncomment region from START to END." + (funcall evilnc-comment-or-uncomment-region-function start end)) + +(defun evilnc--current-line-num () + "Get current line number." + (save-restriction + (widen) + (save-excursion + (beginning-of-line) + (1+ (count-lines 1 (point)))))) + +(defun evilnc--get-line-num (position) + "Get line number at POSITION." + (save-excursion + (goto-char position) + (evilnc--current-line-num))) + +(defun evilnc--find-destination-linenum (last-digits) + "Find closest line whose line number ends with digit LAST-DIGITS. +If LAST-DIGITS is 16, line 16, line 116, and line 216 are candidates. +Then if current line is 17, 16 is the final result." + (let* ((cur-line-num (evilnc--current-line-num)) + (r 1) + (l (length (number-to-string last-digits)))) + (while (> l 0) + (setq r (* r 10)) + (setq l (- l 1))) + (if (>= (mod cur-line-num r) last-digits) + (setq last-digits (+ last-digits r))) + (+ cur-line-num (- last-digits (mod cur-line-num r))))) + +(defun evilnc-do-paragraphs (action num) + "Apply ACTION on NUM paragraphs." + (let* ((i 0) + rlt + (b (point-max)) + (e 0) + linenum) + (catch 'break + (while (< i num) + (when (setq rlt (evilnc--get-one-paragraph-region)) + (setq b (nth 0 rlt)) + (setq e (nth 1 rlt)) + (setq linenum (evilnc--get-line-num e))) + + ;; Original algorithm select all paragraph and comment once + ;; New algorithm comment paragraph by paragraph + (when (<= b e) + (save-excursion + (funcall action b e))) + + ;; prepare for the next paragraph + (cond + ((and rlt (< i num)) + (evilnc--goto-line linenum) + + ;; move to an empty line + (forward-line) + + ;; move to next non-empty line + (re-search-forward "^[ \t]*[^ \t]" nil t) + (if (<= (line-beginning-position) e) + (throw 'break i))) + + (t + (throw 'break i))) + + (setq i (1+ i)))))) + +;;;###autoload +(defun evilnc-comment-or-uncomment-paragraphs (&optional num) + "Comment or uncomment NUM paragraph(s). +A paragraph is a continuation non-empty lines. +Paragraphs are separated by empty lines." + (interactive "p") + (evilnc-do-paragraphs + (lambda (b e) + (evilnc--fix-buggy-major-modes) + (evilnc-comment-or-uncomment-region b e)) + num)) + +;;;###autoload +(defun evilnc-comment-or-uncomment-to-the-line (&optional line-num) + "Comment or uncomment from current line to LINE-NUM line." + (interactive "nLine: ") + (if (not (region-active-p)) + (let* ((b (line-beginning-position)) + (e (line-end-position))) + (save-excursion + (evilnc--goto-line line-num) + (if (< (line-beginning-position) b) + (setq b (line-beginning-position))) + (if (> (line-end-position) e) + (setq e (line-end-position))) + (evilnc--fix-buggy-major-modes) + (evilnc-comment-or-uncomment-region b e))))) + +;;;###autoload +(defun evilnc-quick-comment-or-uncomment-to-the-line (&optional last-digits) + "Comment/uncomment to line number by LAST-DIGITS. +For example, you can use either \ +\\<M-53>\\[evilnc-quick-comment-or-uncomment-to-the-line] \ +or \\<M-3>\\[evilnc-quick-comment-or-uncomment-to-the-line] \ +to comment to the line 6453" + (interactive "p") + (let* ((l (evilnc--find-destination-linenum last-digits))) + (evilnc-comment-or-uncomment-to-the-line l) + (evilnc--goto-line (+ 1 l)))) + +;;;###autoload +(defun evilnc-toggle-invert-comment-line-by-line () + "Please note this command may NOT work on complex evil text objects." + (interactive) + (if evilnc-invert-comment-line-by-line + (setq evilnc-invert-comment-line-by-line nil) + (setq evilnc-invert-comment-line-by-line t)) + (message (if evilnc-invert-comment-line-by-line + "Each line's comment status will be inverted" + "Each line's comment status will NOT be inverted"))) + +;;;###autoload +(defun evilnc-toggle-comment-empty-lines () + "Toggle the flag which decide if empty line will be commented." + (interactive) + (if comment-empty-lines + (setq comment-empty-lines nil) + (setq comment-empty-lines t)) + (message (if comment-empty-lines + "Empty line(s) will be commented" + "Empty line(s) will NOT be commented"))) + +(defun evilnc-visual-line-p () + "In Evil visual line mode." + (and (fboundp 'evil-visual-type) (eq (evil-visual-type) 'line))) + +(defun evilnc-guess-position-at-point () + "Guess current position when evil visual line state is on or off." + (cond + ((and (evilnc-visual-line-p) + (eq (point) (nth 1 (evil-visual-range)))) + ;; In evil visual line state, point is beginning or end visual range + (cons (line-number-at-pos) (1- (current-column)))) + (t + (cons (line-number-at-pos) (current-column))))) + +;;;###autoload +(defun evilnc-comment-or-uncomment-lines (&optional num) + "Comment or uncomment NUM lines. NUM could be negative. + +Case 1: If no region selected, comment/uncomment on current line. +If NUM>1, comment/uncomment extra N-1 lines from next line. + +Case 2: Selected region is expanded to make it contain whole lines. +Then we comment/uncomment the expanded region. NUM is ignored. + +Case 3: If a region inside of ONE line is selected, +we comment/uncomment that region. +CORRECT comment syntax will be used for C++/Java/Javascript." + (interactive "p") + (let* ((orig-pos (evilnc-guess-position-at-point))) + ;; donot move the cursor + ;; support negative number + (cond + ((and (= 1 num) (string-match "^[ \t]*$" (evilnc-sdk-cur-line))) + ;; comment on current empty line + (comment-dwim nil)) + (t + (save-excursion + (when (< num 0) + (evilnc--forward-line (1+ num)) + (setq num (- 0 num))) + (evilnc--operation-on-lines-or-region + (lambda (b e) + (evilnc--fix-buggy-major-modes) + ;; when comment in evil visual state, the cursor may be rogue + (when (evilnc-visual-line-p) (evil-normal-state)) + (evilnc-comment-or-uncomment-region b e)) + num)) + + (evilnc--goto-line (car orig-pos)) + ;; make sure we stay on original line + (goto-char (min (+ (line-beginning-position) (cdr orig-pos)) + (1- (line-end-position)))))))) + +;;;###autoload +(defun evilnc-copy-and-comment-lines (&optional num) + "Copy&paste NUM lines and comment out original lines. +NUM could be negative. + +Case 1: If no region selected, operate on current line. +if NUM>1, comment/uncomment extra N-1 lines from next line + +Case 2: Selected region is expanded to make it contain whole lines. +Then we operate the expanded region. NUM is ignored." + (interactive "p") + ;; support negative number + (when (< num 0) + (evilnc--forward-line (1+ num)) + (setq num (- 0 num))) + + (let* ((original-column (current-column))) + (evilnc--operation-on-lines-or-region + '(lambda (start end) + (evilnc--fix-buggy-major-modes) + (let* ((str (buffer-substring-no-properties start end))) + (cond + (evilnc-original-above-comment-when-copy-and-comment + (let* ((p (point))) + (comment-region start end) + (goto-char start) + (insert-before-markers (concat str "\n")) + (goto-char p))) + (t + (goto-char end) + (newline 1) + (insert-before-markers str) + (comment-region start end))))) + num) + ;; Go to original column after evilnc-copy-and-comment-lines + ;; @see https://github.com/redguardtoo/evil-nerd-commenter/issues/79 + ;; Thanks for Kevin Brubeck (AKA unhammer) for idea/implementation + (move-to-column original-column))) + +;;;###autoload +(defun evilnc-comment-and-kill-ring-save (&optional num) + "Comment lines save origin lines into `kill-ring'. +NUM could be negative. + +Case 1: If no region selected, operate on current line. +;; if NUM>1, comment/uncomment extra N-1 lines from next line + +Case 2: Selected region is expanded to make it contain whole lines. +Then we operate the expanded region. NUM is ignored." + (interactive "p") + ;; support negative number + (when (< num 0) + (evilnc--forward-line (1+ num)) + (setq num (- 0 num))) + + (evilnc--operation-on-lines-or-region + '(lambda (start end) + (evilnc--fix-buggy-major-modes) + (kill-new (buffer-substring-no-properties start end)) + (comment-region start end)) + num)) + +;; {{ for non-evil user only +;;;###autoload +(defun evilnc-copy-to-line (&optional LINENUM) + "Copy from current line to LINENUM line. For non-evil user only." + (interactive "nCopy to line: ") + (if (not (region-active-p)) + (let* ((b (line-beginning-position)) + (e (line-end-position))) + (save-excursion + (evilnc--goto-line LINENUM) + (if (< (line-beginning-position) b) + (setq b (line-beginning-position))) + (if (> (line-end-position) e) + (setq e (line-end-position))) + (kill-new (buffer-substring-no-properties b e)))))) + +;;;###autoload +(defun evilnc-kill-to-line (&optional linenum) + "Kill from the current line to the LINENUM line. For non-evil user only." + (interactive "NKill to line: ") + (if (not (region-active-p)) + (let* ((b (line-beginning-position)) + (e (line-end-position))) + (save-excursion + (evilnc--goto-line linenum) + (if (< (line-beginning-position) b) + (setq b (line-beginning-position))) + (if (> (line-end-position) e) + (setq e (line-end-position))) + ;; +1 because we need remove the CR + (setq e (+ 1 e)) + (if (> e (point-max)) (setq e (point-max))) + (kill-region b e))))) +;; }} + +;;;###autoload +(defun evilnc-version () + "The version number." + (interactive) + (message "3.5.8")) + +(defvar evil-normal-state-map) +(defvar evil-visual-state-map) +(defvar evil-inner-text-objects-map) +(defvar evil-outer-text-objects-map) + +;;;###autoload +(defun evilnc-default-hotkeys (&optional no-evil-keybindings no-emacs-keybindings) + "Setup the key bindings of evil-nerd-comment. +If NO-EVIL-KEYBINDINGS is t, we don't define keybindings in EVIL, +if NO-EMACS-KEYBINDINGS is t, we don't define keybindings in EMACS mode." + (interactive) + ;; Install hotkeys for Emacs mode + (unless no-emacs-keybindings + (global-set-key (kbd "M-;") 'evilnc-comment-or-uncomment-lines) + (global-set-key (kbd "C-c l") 'evilnc-quick-comment-or-uncomment-to-the-line) + (global-set-key (kbd "C-c c") 'evilnc-copy-and-comment-lines) + (global-set-key (kbd "C-c p") 'evilnc-comment-or-uncomment-paragraphs)) + + ;; Install key bindings for evil + (when (and (not no-evil-keybindings) (fboundp 'evil-mode)) + (define-key evil-normal-state-map ",ci" 'evilnc-comment-or-uncomment-lines) + (define-key evil-visual-state-map ",ci" 'evilnc-comment-or-uncomment-lines) + (define-key evil-normal-state-map ",cl" 'evilnc-quick-comment-or-uncomment-to-the-line) + (define-key evil-normal-state-map ",ll" 'evilnc-quick-comment-or-uncomment-to-the-line) + (define-key evil-normal-state-map ",cc" 'evilnc-copy-and-comment-lines) + (define-key evil-visual-state-map ",cc" 'evilnc-copy-and-comment-lines) + (define-key evil-normal-state-map ",cp" 'evilnc-comment-or-uncomment-paragraphs) + (define-key evil-normal-state-map ",cs" 'evilnc-comment-box) + (define-key evil-visual-state-map ",cs" 'evilnc-comment-box) + (define-key evil-normal-state-map ",cr" 'comment-or-uncomment-region) + (define-key evil-normal-state-map ",cv" 'evilnc-toggle-invert-comment-line-by-line) + + ;; comment itself is text object + (define-key evil-inner-text-objects-map evilnc-comment-text-object 'evilnc-inner-commenter) + (define-key evil-outer-text-objects-map evilnc-comment-text-object 'evilnc-outer-commenter) + + (when evilnc-use-comment-object-setup + ;; Install operator for evil text objects + (define-key evil-normal-state-map ",." 'evilnc-copy-and-comment-operator) + (define-key evil-visual-state-map ",." 'evilnc-copy-and-comment-operator) + (define-key evil-normal-state-map ",," 'evilnc-comment-operator) + (define-key evil-visual-state-map ",," 'evilnc-comment-operator)))) + +(defun evilnc-frame-wide-string (s) + "Build summary from S." + (let* ((w (frame-width)) + ;; display kill ring item in one line + (key (replace-regexp-in-string "[ \t]*[\n\r]+[ \t]*" "\\\\n" s))) + ;; strip the whitespace + (setq key (replace-regexp-in-string "^[ \t]+" "" key)) + ;; fit to the minibuffer width + (if (> (length key) w) + (setq key (concat (substring key 0 (- w 4)) "..."))) + key)) + +;;;###autoload +(defun evilnc-imenu-create-index-function () + "Imenu function find comments." + (let* (start end linenum str searching cands) + (save-excursion + (goto-char (point-min)) + ;; learn this skill from etags-select + ;; use simple string search to speed up searching + (setq searching t) + (while searching + ;; C/C++ might use "/* " as comment-start + (setq start (search-forward (string-trim comment-start) (point-max) t)) + ;; OK, it's comment + (cond + ((not start) + (setq searching nil)) + + (t + (setq start (1+ start)))) + + (when (and searching (evilnc-comment-p start)) + (setq linenum (line-number-at-pos start) ) + + (cond + ((string= comment-end "") + (setq end (line-end-position))) + + (t + (setq end (search-forward comment-end (point-max) t)))) + + (cond + ((and end (> end start)) + (setq str (string-trim (buffer-substring-no-properties start end))) + ;; no empty line + (setq str (replace-regexp-in-string "[\r\n]+" "\n" str)) + ;; could be multi-lines comment + (let* ((a (split-string str "[\r\n]+")) + (pre-pattern (concat "^[ \t]*[" + (string-trim comment-start) + "]*[ \t]*")) + (post-pattern (concat "[ \t]*[" + (string-trim comment-end) + "]*[ \t]*$"))) + ;; remove empty lines + (setq a (delq nil (mapcar (lambda (s) + (setq s (replace-regexp-in-string pre-pattern "" s)) + (setq s (replace-regexp-in-string post-pattern "" s)) + (setq s (string-trim s)) + (unless (string-match-p "^[ \t]*$" s) s)) + a))) + (setq str (mapconcat 'identity a "\n" ))) + + + (when (and (not (string-match-p "^[ \t\n\r]*$" str)) + (> (length str) evilnc-min-comment-length-for-imenu)) + (let* ((m (make-marker))) + (set-marker m start) + (push (cons (evilnc-frame-wide-string (format "%d:%s" linenum str)) m) + cands))) + + (goto-char (min (1+ end) (point-max)))) + + (t + (setq searching nil)))))) + (nreverse cands))) + +(defun evilnc-html-comment-region (start end) + "Comment region between START and END." + (save-excursion + (goto-char end) + (insert (evilnc-html-comment-end)) + (goto-char start) + (insert (evilnc-html-comment-start)))) + +(defun evilnc-html-uncomment-region (start end) + "Uncomment HTML tag between START and END." + (let* (mark-start-pos + mark-end-pos + (len-comment-start (length (evilnc-html-comment-start)))) + (save-excursion + (goto-char start) + (setq mark-start-pos (search-forward (evilnc-html-comment-start) end t)) + (goto-char end) + (setq mark-end-pos (search-backward (evilnc-html-comment-end) start t)) + (when (and mark-start-pos mark-end-pos) + (goto-char mark-end-pos) + (delete-char (length (evilnc-html-comment-end))) + (goto-char (- mark-start-pos len-comment-start)) + (delete-char len-comment-start))))) + +(defun evilnc-html-tag-comment-p (start) + "Test if html tag comment is at position START position." + (save-excursion + (goto-char start) + (string-match-p (concat "^[ \t]*" (regexp-quote (evilnc-html-comment-start))) + (evilnc-sdk-cur-line)))) + +;;;###autoload +(defun evilnc-comment-or-uncomment-html-tag () + "Comment or uncomment html tag(s). +If no region is selected, current tag under focus is automatically selected. +In this case, only one tag is selected. +If users manually select region, the region could cross multiple sibling tags +and automatically expands to include complete tags. +Users can press \"v\" key in evil mode to select multiple tags. +This command is not dependent on any 3rd party package." + (interactive) + (let* (beg end beg-line-beg end-line-end) + (cond + ((region-active-p) + (setq beg (region-beginning)) + (setq end (region-end)) + (save-excursion + (goto-char beg) + (setq beg-line-beg (line-beginning-position)) + (goto-char end) + (setq end-line-end (line-end-position)) + (when (< beg-line-beg (line-beginning-position)) + ;; it's multiple lines region + (goto-char beg-line-beg) + (setq beg (re-search-forward "^[ \t]*" end t)) + (setq end end-line-end)))) + (t + (save-excursion + (sgml-skip-tag-backward 1) + (setq beg (point)) + (setq beg-line-beg (line-beginning-position)) + (sgml-skip-tag-forward 1) + (setq end (point)) + (setq end-line-end (line-end-position))))) + + (cond + ((evilnc-html-tag-comment-p beg) + ;; make sure all tags plus comment marks are selected + (evilnc-html-uncomment-region beg-line-beg end-line-end)) + (t + ;; the whole tags is already selected + (evilnc-html-comment-region beg end))))) + +;;;###autoload +(defun evilnc-comment-or-uncomment-html-paragraphs (&optional num) + "Comment or uncomment NUM paragraphs contain html tag. +A paragraph is a continuation non-empty lines. +Paragraphs are separated by empty lines." + (interactive "p") + (evilnc-do-paragraphs + (lambda (b e) + (ignore e) + ;; {{ make sure tag is focused + (goto-char b) + (sgml-skip-tag-forward 1) + ;; }} + (evilnc-comment-or-uncomment-html-tag)) + num)) + +(defun evilnc-comment-box (&optional num) + "Comment out NUM lines, putting it inside a box. NUM could be negative. +If NUM is nil, comment out current paragraph. This command uses `comment-box'." + (interactive "P") + (let* ((orig-pos (evilnc-guess-position-at-point)) + range) + + (cond + ((or (not num)) + (when (setq range (bounds-of-thing-at-point 'paragraph)) + (evilnc--fix-buggy-major-modes) + (when (evilnc-visual-line-p) (evil-normal-state)) + (comment-box (car range) (cdr range) 1))) + (t + (save-excursion + (when (< num 0) + (evilnc--forward-line (1+ num)) + (setq num (- 0 num))) + (evilnc--operation-on-lines-or-region + (lambda (b e) + (evilnc--fix-buggy-major-modes) + ;; when comment in evil visual state, the cursor may be rogue + (when (evilnc-visual-line-p) (evil-normal-state)) + (comment-box b e 1)) + num)))) + + (unless (eq (evilnc-guess-position-at-point) orig-pos) + (evilnc--goto-line (car orig-pos)) + ;; make sure we stay on original line + (goto-char (min (+ (line-beginning-position) (cdr orig-pos)) + (1- (line-end-position))))))) + +;; Attempt to define the operator on first load. +;; Will only work if evil has been loaded +(with-eval-after-load 'evil + (require 'evil-nerd-commenter-operator)) + +(provide 'evil-nerd-commenter) +;;; evil-nerd-commenter.el ends here |
