Homepage: https://github.com/twlz0ne/separedit.el
Author: Gong Qijian
Updated:
Edit comment/string/docstring/code block in separate buffer
[![CI](https://github.com/twlz0ne/separedit.el/workflows/CI/badge.svg)](https://github.com/twlz0ne/separedit.el/actions?query=workflow%3ACI) [![MELPA](https://melpa.org/packages/separedit-badge.svg)](https://melpa.org/#/separedit) # separedit.el Edit comment/string/docstring/code block in separate buffer with your favorite mode. +----------+ Edit +-----------+ Edit +-----------+ | | ---------------------> | edit | ---------------------> | edit | ... | | point-at-comment? | buffer | point-at-comment? | buffer | | source | point-at-string? | | point-at-string? | | ... | buffer | point-at-codeblock? | (markdown | point-at-codeblock? | (markdown | ... | | point-at-...? | orgmode | point-at-...? | orgmode | | | <--------------------- | ...) | <--------------------- | ...) | ... +----------+ Commit changes +-----------+ Commit changes +-----------+ {{TOC}} ## Installation Clone this repository, or install from MELPA. Add the following to your `.emacs`: ``` elisp (require 'separedit) ;; Key binding for modes you want edit ;; or simply bind ‘global-map’ for all. (define-key prog-mode-map (kbd "C-c '") #'separedit) (define-key minibuffer-local-map (kbd "C-c '") #'separedit) (define-key help-mode-map (kbd "C-c '") #'separedit) (define-key helpful-mode-map (kbd "C-c '") #'separedit) ;; Default major-mode for edit buffer ;; can also be other mode e.g. ‘org-mode’. (setq separedit-default-mode 'markdown-mode) ;; Feature options ;; (setq separedit-preserve-string-indentation t) ;; (setq separedit-continue-fill-column t) ;; (setq separedit-write-file-when-execute-save t) ;; (setq separedit-remove-trailing-spaces-in-comment t) ``` ## Usage - Move the cursor to a comment/string/code block or any supported place. - Press C-c '. or press C-u C-c ' to starting edit with manually selected major mode. Can also press C-c ' on an active region. Following are default keys in edit buffer: | Key | Function | Summary | |:-------------------|:---------------------------------------------------|:--------------------------------------------------------------------| | C-c C-c | `separedit-commit` | Commit changes and close edit buffer | | C-x C-s | `separedit-save` | Commit changes (even write source file) without closing edit buffer | | C-c C-k | `separedit-abort` | Discard changes and close edit buffer | | C-c ' | `separedit` or follow the settings of markdown/org | Open a new edit buffer | ### Edit comment `separedit` use **continuity** as basis for determining whether it is a comment **block** or **line**. Continuous means that there is no barrier (e.g. code or blank line) between the end of previous line and the beginning of next line, for example: /* * this is a * comment block */ // // this is also a // comment block // // // this is another // comment block // code 1 /* all this are comment lines */ code 2 /* all this are comment lines */ code 3 // all this are comment lines code 4 // all this are comment lines By setting `separedit-default-mode` to choose the mode (e.g. `markdown-mode` or `org-mode`) for edit buffer. In edit buffer, the comment delimiter will be removed, for example (█ represents the cursor): source buffer -> edit buffer -> edit buffer /* * # Example█ # Example * * ``` C ``` C * foo("bar"); foo("bar");█ foo("bar"); * ``` ``` */ // * Example█ * Example // // #+BEGIN_SRC C #+BEGIN_SRC C // foo("bar"); foo("bar");█ foo("bar"); // #+END_SRC #+END_SRC ### Edit string `separedit` provides convenience for editing escaped strings, if there are nested string or code block, just continue press C-c ' to enter a new edit buffer: source buffer -> edit buffer -> edit buffer "a█\"b\\\"c\\\"\"" a"b█\"c\"" b"c" ### Edit code block `separedit` also support for editing code block directly in comment or string: source buffer -> edit buffer ",--- elisp | (foo \"bar\")█ (foo "bar") `---" /* * ``` C * foo("bar");█ foo("bar"); * ``` */ If the language identifier of code block is omitted, the edit buffer uses the same mode as the source buffer. ### Edit heredoc The heredoc marker can be used to specify the language: source buffer -> edit buffer (css-mode) ...<#define█FOO(a, b) do { \ do { auto _a = (a); \ auto _a = (a); auto _b = (b); \ auto _b = (b); } while (false) } while (false) ### Edit value form of variable in help/helpful buffer Describe a variable, move cursor to the local/global value form, press C-c ' to edit it. ### Edit minibuffer Don't get stuck in minibuffer, press C-c ' to open a edit buffer. ### Edit in vterm Make sure the the vterm [Directory tracking and Prompt tracking](https://github.com/akermu/emacs-libvterm#directory-tracking-and-prompt-tracking) is set correctly. Then put the cursor after prompt, press C-c ' to start a new edit, or C-p C-c ' to edit previous command. ## Customization ### Change key bindings in edit buffer If you don't like the default key bindings in edit buffer, you can change it: - `separedit-save-key` - `separedit-entry-key` - `separedit-abort-key` - `separedit-commit-key` ### Add support for a new major mode 1. Add the start/end delimiter of block style comment to `separedit-comment-encloser-alist`. 1. Add the delimiter of each comment line to `separedit-comment-delimiter-alist`. 1. Add the string (including docstring) quotes to `separedit-string-quotes-alist`. 1. Add definition to `separedit-string-indent-offset-alist` if there is base indent offset in docstring. 1. Add a mode name to `separedit-not-support-docstring-modes` if not support docstring. ### Add support for a new code block 1. Add a set of regexps matching the new code block to `separedit-block-regexp-plists`. 1. Add a language name to `separedit-code-lang-modes` if can't get mode by simply adding suffix `-mode`. ### Preserving indentation of block in string If `separedit-preserve-string-indentation` is non-nil, the indentation of string block will be preseved in edit buffer, e.g: ``` source buffer edit buffer +--------------------+ +--------------------+ | def func(): | | Usage: | | ''' | | func() | | Usage: | -> | | | func() | | | | ''' | | | | pass | | | +====================+ +====================+ ``` No only for the docsting, normal string are also supported: ``` source buffer edit buffer +--------------------+ +--------------------+ | emacs \ | | (progn | | --batch \ | | ...) | | --eval "(progn | -> | | | ...)" | | | | | | | +====================+ +====================+ ``` ### Continue fill-column width in edit buffer If `separedit-continue-fill-column` is non-nil, use the remaining fill-width in edit buffer: ``` source buffer edit buffer // // this is a this is a // comment block comment block // |<---->|<------------>| |<------------->| | | | | '-- available width for --' | edit buffer used width ``` You may also like to enable `auto-fill-mode` in edit buffer: ```elisp (add-hook 'separedit-buffer-creation-hook #'auto-fill-mode) ``` ## Some extended usage ### Combine multipe adjacent blocks as a single edit block ``` elisp (defun separedit//region-of-el-commentary () (save-excursion (goto-char (point-min)) (when (re-search-forward "^;;; Commentary:\n+") (let ((begin (point))) (when (re-search-forward "\n;;; .*$" nil t) (goto-char (match-beginning 0)) (list begin (point))))))) (defun separedit/edit-el-commentary () "Edit whole commentary section as a single block." (interactive) (let ((separedit-leave-blank-line-in-comment t)) (separedit-dwim (apply #'separedit-mark-region `(,@(separedit/region-of-el-commentary) markdown-mode))))) ``` ### Break long lines in comment ``` elisp (defun separedit/re-fill () (interactive) (let ((separedit-continue-fill-column t)) (with-current-buffer (separedit-dwim) (fill-region (point-min) (point-max)) (execute-kbd-macro (kbd "C-c C-c"))))) ``` ### Eval multiple-line sexp in comment ``` elisp (defun separedit/eval-last-sexp-in-comment () (interactive) (let ((separedit-default-mode 'emacs-lisp-mode) (separedit-inhibit-edit-window-p t)) (with-current-buffer (separedit) (unwind-protect (call-interactively #'eval-last-sexp) (separedit-abort))))) (define-key emacs-lisp-mode-map (kbd "C-x C-e") (lambda () (interactive) (call-interactively (if (separedit--point-at-comment) #'separedit/eval-last-sexp-in-comment #'eval-last-sexp)))) ```