Fuco1 / smartparens

Minor mode for Emacs that deals with parens pairs and tries to be smart about it.
GNU General Public License v3.0
1.84k stars 193 forks source link

Feature request: allow legal closing delimiter in strict mode #960

Open yuhan0 opened 5 years ago

yuhan0 commented 5 years ago

Expected behavior

With smartparens-strict-mode enabled, I occasionally run into situations where the buffer is in an unbalanced state with missing closing delimiters.

It would be nice to "correct" the situation by simply typing the closing parens in the appropriate places, like a regular editor would let you do.

Actual behavior

Nothing is inserted, the expressions remain unbalanced with the message printed:

"We can not insert unbalanced closing delimiter in strict mode"

This can be quite frustrating - after all the intention is to insert a balanced closing delimiter, and strict mode should be smart enough to detect that and allow the "correction" to go through.

It would be even better if Smartparens could detect the closest unbalanced opening and automatically insert the corresponding closing pair, e.g. allowing the user to type an easy-to-reach closing pair such as ] to insert #-} or </div> or \end{section}

Steps to reproduce the problem

Environment & version information

In recent enough smartparens you can call M-x sp-describe-system to generate this report. Please fill manually what we could not detect automatically. Edit the output as you see fit to protect your privacy.

yuhan0 commented 5 years ago

Note that this is slightly different from #300, because it can often be ambiguous where closing parens should go:

;; unbalanced
(defn foo
  [bar {:a 1 baz (quux))
;;============================

;; a few possible "solutions"
(defn foo
  [bar {:a 1 baz (quux)}])

(defn foo
  [bar {:a 1} baz] (quux))

(defn foo
  [bar] {:a 1} baz (quux))

So an automatic function to close all parens won't be sufficient - the user should be able to navigate to each point and insert them manually.

Fuco1 commented 5 years ago

The logic here is actually cheating a bit. We never even check if the buffer is unbalanced, we just assume it is and prevent the user from inserting the closing delimiter.

This can be improved for sure, it sounds like a nice challenge. The main issue here, as always, is performance. Since we don't keep any state sometimes this might require a re-parse of the entire buffer and that can blow up.

As a shitty solution I can offer you to use C-q with which you can insert anything and smartparens will ignore all the checks.

yuhan0 commented 5 years ago

Yeah, I've been using the C-q workaround so far, or r from evil normal state, but it's definitely not optimal in terms of editing flow.

I would be glad to try helping with this, any pointers on where/how to start tackling it? I tried briefly to read through the relevant portions of the code but the API seems quite complicated.

Fuco1 commented 5 years ago

I think that sp-skip-closing-pair is the place.

Now that I think about it more... it might be possible to do this somehow because we actually do the parse anyway to determine the point out of which we want to jump out. So the data is in a sense computed but then thrown away because on failure the parser just returns nil and not, for example, the furthest opening delim which caused the failure to match. With this information we could determine if we are over- or under- balanced, and in the under- case allow the insertion.

The two situations are:

(foo bar |  ;; under-balanced, allow insertion

(foo bar) | ;; over-balanced, do not allow insertion.

The buffer actually parsed would be (foo bar ) or (foo bar )) (i.e. we temporarily insert the delimiter).

So at least this simple case should be quite easy to implement. As for inserting </div> or something similar, I'm not so sure.