cute-jumper / embrace.el

Add/Change/Delete pairs based on `expand-region', similar to `evil-surround'.
129 stars 11 forks source link

Wow and Org-related idea #1

Open alphapapa opened 8 years ago

alphapapa commented 8 years ago

Hi there,

Just saw your post on /r/emacs, and this looks great! Can't wait to try it.

I have an idea: I have this code which I use to enclose regions in Org blocks and change Org block types, and it would seem to go very well with embrace. Maybe you'd like to integrate it? I can imagine a binding like C-, a o to enclose a region in an Org block...

Here's the code, feel free to use however you like. :)

(defun ap/org-insert-structure-template-or-enclose-region ()
    "Insert structure block template.  When region is active, enclose region in block."
    (require 's)
    (interactive)
    (let* ((template (ap/org-read-structure-template))
           (text "")
           enclosed-text)
      (when (use-region-p)
        (setq text (buffer-substring-no-properties (region-beginning) (region-end)))
        (delete-region (region-beginning) (region-end)))
      (setq enclosed-text (s-replace "?" text template))
      (insert enclosed-text)
      (backward-char (- (length enclosed-text) (length (s-shared-start enclosed-text template))))))

(defun ap/org-change-block-types ()
    "Change the type of org-mode block at point, or blocks in region."
    (interactive)
    (if (use-region-p)
        (progn
          (deactivate-mark)
          (goto-char (region-beginning))
          (while (re-search-forward  "^ *#\\+BEGIN_" (region-end) nil)
            (ap/org-change-block-type-at-point)))
      (ap/org-change-block-type-at-point)))

(defun ap/org-change-block-type-at-point ()
    "Change type of org-mode block at point."
    (interactive)
    (unless (ap/org-in-block-p)
      (error "Not in an org-mode block."))
    (let* ((template (ap/org-read-structure-template))
           (case-fold-search t)
           (re "#\\+begin_\\(\\sw+\\)")
           (block-bounds (ap/org-block-boundaries))
           (block-beg (car block-bounds))
           (block-end (cdr block-bounds))
           (contents (ap/org-block-contents))
           new-block)
      ;; Insert contents into template
      (setq new-block (replace-regexp-in-string (rx "?") contents template))
      ;; Remove extra newline from e.g. SRC blocks
      (setq new-block (replace-regexp-in-string (rx "\n\n#+END") "\n#+END" new-block))
      ;; Replace old block with new one
      (goto-char block-beg)
      (delete-region block-beg block-end)
      (insert new-block)
      ;; Position cursor (especially for SRC blocks, allowing the user to enter the type)
      (search-backward-regexp re)
      (search-forward-regexp (rx space))))

(defun ap/org-block-contents (&optional whole)
    "Return contents of current \"BEGIN_...\" block.
When WHOLE is non-nil, include enclosing meta lines."
    (let ((bounds (ap/org-block-boundaries (not whole))))
      (buffer-substring-no-properties (car bounds) (cdr bounds))))

(defun ap/org-block-boundaries (&optional contents)
    "Return (BEGINNING . END) of current \"#+BEGIN_...\" block.
If CONTENTS is non-nil, return the boundaries of the block's
contents rather than the entire block."
    (let ((case-fold-search t)
          (re "#\\+begin_\\(\\sw+\\)")
          block-beg block-end contents-beg contents-end)
      (save-excursion
        ;; Get block
        (unless (looking-at re)
          ;; If point is in the middle of the "#+BEGIN...",
          ;; `search-backward-regexp' fails, so go to end of line first.
          (end-of-line)
          (condition-case nil
              (search-backward-regexp re)
            (error "Not in a block.")))
        (setq block-beg (point))
        (setq block-end (search-forward-regexp (concat (rx bol (optional (1+ space)) "#+end_") (match-string 1))))
        (goto-char block-beg)
        (forward-line)
        (setq contents-beg (point))
        (goto-char block-end)
        (end-of-line 0)
        (setq contents-end (point)))
      (if contents
          `(,contents-beg . ,contents-end)
        `(,block-beg . ,block-end))))

(defun ap/org-in-block-p ()
    "Non-nil when point belongs to a block.

Return first block name matched, or nil.  Beware that in case of
nested blocks, the returned name may not belong to the closest
block from point."
    (save-match-data
      (let ((case-fold-search t)
            (lim-up (save-excursion (outline-previous-heading)))
            (lim-down (save-excursion (outline-next-heading))))
        (org-between-regexps-p "^[ \t]*#\\+begin_" "^[ \t]*#\\+end_"
                               lim-up lim-down))))

(defun ap/org-read-structure-template ()
    "Read org-mode structure template with completion.  Returns template string."
    (let* ((templates (map 'list 'second org-structure-template-alist))
           (prefixes (map 'list (lambda (tp)
                                  ;; Get template and pre-whitespace prefix for completion
                                  (reverse (s-match (rx (group
                                                         (1+ (not (any "\n" space))))
                                                        (1+ anything))
                                                    tp)))
                          templates))
           (prefix (completing-read "Template: " prefixes nil t))
           (template (second (assoc prefix prefixes))))
      template))
cute-jumper commented 8 years ago

Thanks! It looks nice. I'm thinking about adding some default settings to different major modes. This might be a good use in org-mode. I'll give it a shot later today.

alphapapa commented 8 years ago

Cool. BTW, is your blog on Planet Emacsen? I just noticed it from your GitHub profile, but I don't think I've seen it on PE before.

cute-jumper commented 8 years ago

No. It's not. I'm not good at writing articles :sob: Sometimes I write some Emacs stuffs on my blog, but I'm not aware whether people read it or not.

alphapapa commented 8 years ago

I must respectfully disagree. For example, this article is as good as anything else on Planet Emacsen: http://cute-jumper.github.io/emacs/2015/12/18/quick-summary-a-few-emacs-packages-ive-written You should definitely submit your blog for inclusion, at least for the Emacs-tagged posts. :)

cute-jumper commented 8 years ago

Thanks for the nice words! I would consider adding it to Planet Emacsen next time I write something about Emacs.

cute-jumper commented 8 years ago

Hi,

I've just pushed a quick fix to add org block support. Could you give some feedback if you have time to try out? To use it

(add-hook 'org-mode-hook 'embrace-org-mode-hook)

The key for the block is l. If you bind C-, to embrace-commander, you can use C-, c l l inside a org block to change the block type.

cute-jumper commented 7 years ago

I'm closing this now. If any problems pop up, feel free to reopen this.

alphapapa commented 7 years ago

Hey, I'm sorry, I missed this! I just tried it and it works great!

One question, when I change to a src block, it only offers emacs-lisp as the language. Is this intended?

cute-jumper commented 7 years ago

Hey, I'm sorry, I missed this!

No problem at all!

it only offers emacs-lisp as the language. Is this intended?

No. We use org-babel-load-languages to determine the available languages. Have you checked the value of the org-babel-load-languages?

alphapapa commented 7 years ago

Ah, yes, that's the issue. The problem is, users have to add languages to that manually, so it won't be an accurate representation of the language modes available in Emacs. And some language modes aren't suitable for Babel anyway (e.g. HTML).

cute-jumper commented 7 years ago

I'm not aware of a good and reliable way to find out all the available major modes for programming languages. So I have to assume if users want to use BEGIN_SRC, they would add the corresponding languages to that list (and I remember that's what the Org manual asks people to do, right?), and even if it is not in the list, you can just ignore the completing suggestions and enter a language identifier directly. Do you have any good solution to this problem?

And you remind me of the HTML, which has a different syntax for exporting after Org 9.0. I may need to update the code as well.

alphapapa commented 7 years ago

You're right, there's not a really clean solution. The best I can find at the moment is some combination of auto-mode-alist and interpreter-mode-alist, with the -mode chopped off each symbol. That would leave some inappropriate modes in the list, but you could leave those in and it wouldn't be a big deal, or you could blacklist the few non-source-code modes and probably have fewer hardcoded modes than if you whitelisted the ones you want. Just an idea. :)

cute-jumper commented 7 years ago

The best I can get right now is to extract information from the custom-type of org-babel-load-languages:

(mapcar (lambda (x) (car (last x)))
        (cdr (plist-get
              (cdr (get 'org-babel-load-languages 'custom-type))
              :key-type)))

This should include all the languages that org-babel supports.

alphapapa commented 7 years ago

Right, but source blocks can be used for languages that org-babel doesn't support, i.e. they aren't always intended to be executed with Babel. :)

cute-jumper commented 7 years ago

Are there many languages that can't be "executed", like HTML? If not too much, we could just maintain a list and allow users to customize that variable.

alphapapa commented 7 years ago

I don't have any numbers, but I'm pretty sure that there are far more language-specific major modes in "Emacsland" than languages supported by Babel. That's why I'd suggest using something like auto-mode-alist and chopping the -mode off each symbol. It won't be perfect, but I think it might be something like 70-80% accurate, and that'd be far better than only offering the languages that users have manually added to org-babel-load-languages. For example, for me that is approximately one, elisp; I occasionally execute Python in Babel, and specifically enable it in a certain Org file. Other than that, my use of Org source blocks is not for Babel.

Just some ideas. I'm not trying to tell you what to do with your package. :)

cute-jumper commented 7 years ago

I'm not trying to tell you what to do with your package

No offense taken. :) Personally, I almost always use src blocks with Babel. That's why I'm wondering what other useful cases there will be.

I think the src blocks are mainly used for programming languages, which are supposed to be executed. The extracted custom-type values contain 44 languages that Babel currently supports, which should cover most common use cases (hopefully). But as you said, HTML could be an example that is not intended to be used for Babel. The problem of using auto-mode-alist is that it may contain too many major modes that are not related to programming languages, and that makes me hesitant to take the approach.

Are you saying that the non-programming use cases could also be very common in src blocks? I'm assuming the opposite, so adding things like HTML to a customizable variable by default and letting users add their special "languages" is the best compromise I could think of right now.

alphapapa commented 7 years ago

Are you saying that the non-programming use cases could also be very common in src blocks?

I think that is fairly common, yes. For example, non-executable formats can be shown in source blocks, like INI, YAML, JSON, etc, all of which have Emacs major modes. And while many types of languages can be executed in Babel, not all of them are generally suitable for that, e.g. C, Rust, etc., and I doubt many users would have languages like that in their Babel load list.

cute-jumper commented 7 years ago

Hmm... In my Emacs, it has over 300 major modes, which seems too many. I guess I'll just combine auto-mode-alist and the custom tags of org-babel-load-languages. Should be able to cover most common cases.

alphapapa commented 7 years ago

Yeah, that is a lot, and it seems like way too many. I could live with it, using Helm for completion, but it probably wouldn't be a good default.

cute-jumper commented 7 years ago

Combining org-bable-load-languages and auto-mode-alist produces 134 results in my Emacs. I guess it should be enough in most cases. I've pushed the changes and you could try it out. :-)

alphapapa commented 7 years ago

Thanks, I'll give it a try soon. :)