aboutsummaryrefslogtreecommitdiff
path: root/.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-operator.el
diff options
context:
space:
mode:
Diffstat (limited to '.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-operator.el')
-rw-r--r--.config/emacs/lisp/evil-nerd-commenter/evil-nerd-commenter-operator.el402
1 files changed, 402 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: