smartparens icon indicating copy to clipboard operation
smartparens copied to clipboard

Way to wrap region and stay inside pairs

Open andreyorst opened this issue 3 years ago • 5 comments

Sorry if this feature was asked or presented in Smartparens, I was unable to find it, and hack upon Smartparens to implement it myself. This is similar to https://github.com/Fuco1/smartparens/issues/508

TL;DR

Provide a way to wrap region and stay inside pairs with point resting at beginning or end of region depending on what key is pressed without depending on region orientation. E.g.

(|some text) ;; user selected "some text" and pressed "("
(some text|) ;; user selected "some text" and pressed ")"
[|some text] ;; user selected "some text" and pressed "["
[some text}] ;; user selected "some text" and pressed "]"
;; and so on

When writing in Lisp languages it is extremely useful, and Paredit does this for opening parenthesis, which I find very helpful.

More detailed explanation

Currently it is possible to select a region and press ( or ) and have this outcome:

(% represents region, | represents point) Region:

%some text|

press (:

(some text)|

It works the same with ) Region:

%some text|

press ):

(some text)|

If we reverse region, we get the desired outcome when pressing (. Region:

|some text%

press (:

(|some text)

But if we press ) we are out of the pairs again: Region:

|some text%

press ):

(some text)|

There also is a variable sp-wrap-respect-direction, which I can set to true, and this will work mostly like I want it to without manually reversing region, however it keeps the point outside of the parentheses:

Region:

%some text|

press (:

|(some text)

It works the same with ) Region:

%some text|

press ):

(some text)|

andreyorst avatar Oct 14 '20 18:10 andreyorst

A possible solution was suggested in Emacs chat in telegram:

(setq sp-wrap-respect-direction t)

(defun sp--wrap-fix-cursor-position (_id action _context)
  (when (and (eq action 'wrap)
             (eq (point)
                 ;; position before delimiter
                 (marker-position (sp-get sp-last-wrapped-region :beg))))
    (goto-char
     ;; position after delimiter
     (sp-get sp-last-wrapped-region :beg-in))))

(sp-pair "(" nil :post-handlers '(:add sp--wrap-fix-cursor-position))

This makes ( behave like in Paredit.

andreyorst avatar Oct 15 '20 16:10 andreyorst

So to summarize, you want the exact same behaviour as we get when sp-wrap-respect-direction and additional always (or only in some cases?) to position the cursor inside the pair. Is this correct?

Fuco1 avatar Oct 16 '20 09:10 Fuco1

Yes, I want a toggle that will make it possible to define this behaviour for all pairs. Like sp-wrap-respect-direction, makes smartparens respect direction, I want a way of being able make it stay inside or go outside by toggling some variable.,

I mean I do this all the time, when wrapping stuff in Org mode, and want to continue typing inside wrapped region right away. My workflow requires this much more often, compared to when I simply forgot to wrap something and want to type additional text after wrapping. E.g.

I have this text in a Org mode buffer:

Function sum-of-squares will compute the sum of squares....

I've forgotten to wrap "sum-of-squares" with =. Which means that I would select it, and press =, with sp-wrap-respect-direction set to t producing:

Function |=sum-of-squares= will compute the sum of squares....

(| represents the cursor) This makes little sense, as I would not want to add more text before the already existing text. More than that, with org-hide-emphasis-markers set to t it is impossible to acutally add something before sum or after squares without deleting either text of the =

With the option I'm asking set to t, pressing = it should produce

Function =|sum-of-squares= will compute the sum of squares....

Which makes much more sense in the long run, because = is not directional pair, and since there's already text after wrapped region so the chances I would want to add something after the last = are minimal.

I also thing, that when writing code, you wrap things only if you're changing the existing code, and wrapping usually means that you will need to change things inside wrapped region. Maybe you're changing things and then wrap, but I'm not and hence this setting would be very useful :)

andreyorst avatar Oct 16 '20 20:10 andreyorst

Answering your question: yes, always

andreyorst avatar Oct 17 '20 09:10 andreyorst

What you say makes sense and in org mode I also often fight the same issue (note that you can sp-down-sexp to jump in the =asd= pair).

I think I would like this with the string-like delimiters, but for code I'm not so sure. I don't consciously remember this ever annoying me too much. Since a per-pair setting is already mentioned in your answer, adding a global setting might make sense).

I think instead of adding more settings I will extend sp--run-hook-with-args (which fires the events based on the pair) to also fire a global hook. This way you can add your function from above to this hook and it will always trigger.

Overall I'm quite happy that even after 8 years the extension API/configuration is still capable of solving almost any issue... but it is clunky as hell, especially for newcomers. Bad thing is I don't quite have ideas how to make it nicer and also backward compatible.

Fuco1 avatar Oct 19 '20 20:10 Fuco1