[![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`.
2. Add the delimiter of each comment line to
`separedit-comment-delimiter-alist`.
3. Add the string (including docstring) quotes to
`separedit-string-quotes-alist`.
4. Add definition to `separedit-string-indent-offset-alist` if there is base
indent offset in docstring.
5. 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`.
2. 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))))
```