parinfer / parinfer.js

Let's simplify the way we write Lisp
https://shaunlebron.github.io/parinfer
MIT License
1.76k stars 40 forks source link

Smarter dedent behavior to prevent unintended sibling adoption #143

Closed shaunlebron closed 7 years ago

shaunlebron commented 7 years ago

Unified mode is sort of the expected behavior of a combined mode when changes option is given to Indent Mode.

from clojurians slack (lazy screenshot paste since clojurians log butchers the code blocks)

screen shot 2017-07-18 at 1 42 14 am screen shot 2017-07-18 at 1 42 31 am
shaunlebron commented 7 years ago

none of these options are good, so I'm looking at a fourth option to allow these invalid intermediate states, like we did here: http://shaunlebron.github.io/parinfer/#knowing-when-parens-move-in-indent-mode

I will try clamping the paren trails opposite to any open-paren on the current cursor line. That should prevent swallowing subsequent lines unintentionally, at least until the cursor is moved away.

With this extra rule, I want to provide more affordance for when a paren may be displaced after cursor movement. I think returning a list of warnings like we do errors would be sufficient for this.

Best-case example

For example:

(println |{:a 1
           :b 2}
   bar)

Deleting println shows:

(|{:a 1
   :b 2}
       ^ warning: paren is currently being held (will be corrected if still invalid after editing)
   bar)

The paren will have to be fixed if problem is still present after user is done editing (indicated by cursor position). More on this later.

Typing println shows (and the warning clears)

(print| {:a 1
         :b 2}
   bar)

Worse-case Example

If bar was originally indented like so:

(println |{:a 1
           :b 2}
         bar)
(print| {:a 1
         :b 2}
             ^ warning: paren is currently being held (will be corrected if still invalid after editing)
         bar)

If the user saves the file at this point, re-opening the file later will cause Paren Mode to correct indentation to preserve the intended structure (likewise corrected by Parlinter if project uses it).

But if the user moves the cursor away, we can add prevCursorLine option to detect when to run Paren Mode in place (when cursor movement would result in structure changes):

(print {:a 1
        :b 2}
       bar)
       ^ dedented by 1 space to prevent structure change after cursor movement

Other thoughts

We can not perform the indentation correction to prevent structure changes if we are not worried about it. But it seems like a good idea to follow what @DogLooksGood said:

IMO, when you want to edit some symbols, literals, etc, parens should never be changed. the indentation of bar, shouldn't affect the code struct.

If we do perform the correction, I also have to think about implications of the cursor moving as a result of change.

DogLooksGood commented 7 years ago

I have a idea, not sure if it covors all cases.

  1. Edit the indentation(this means no non-space character before caret), call Indent Mode.
  2. Start typing on new line, call Indent Mode.
  3. Modify parens, call Indent Mode.
  4. Otherwise, call Paren Mode, and this mode will keep structure safe.
  5. For Clojure, you can't move items out of maps or vectors, unless your caret is inside this map or vector.
shaunlebron commented 7 years ago

@DogLooksGood that could be a better idea when we start looking at characters inside the changes option to perform inference. there are a lot of implications to explore there.

shaunlebron commented 7 years ago

I think this new dedent behavior might warrant a separate "smart mode" function. Right now, the presence of the changes option triggers this smart behavior, but this new dedent behavior is considered part of this smart mode, but must be on all the time since it is dependent on cursor position, not changes. Thus we need an explicit separate mode if we don't want this new behavior affecting original indent mode.

shaunlebron commented 7 years ago

pasted from a brainstorm file:


|| adjust indentation of `4` to respect structure of these parens only
||
||
|| |||| lock the position of the close-parens opposite to these
|| ||||
vv vvvv
((|((((1
      2
      3)))))
           ^
           |
           | indentation of `4` must be corrected to respect this paren
 4)

NON-ANNOTATED VERSION OF OUTPUT:
((((((1
      2
      3)))))
 4)

INTERESTING, so moving to the right results in a full correction.

|| adjust indentation of `4` to respect structure of these parens only
|||||
|||||
||||| | lock the position of the close-parens opposite to these
||||| |
vvvvv v
(((((|(1
      2
      3)))))
        ^^^^
        ||||
        |||| indentation of `4` must be corrected to respect these parens
     4)

NON-ANNOTATED VERSION OF OUTPUT:
((((((1
 ^    2
      3)))))
 4)

WHAT WE DISCOVERED

  the resolution for INTERSECTING AREAS OF TENSION are equivalent?

WHAT the hell is INTERSECTING AREAS OF TENSION

  Just an EXPRESSION ending with a Paren Trail containing multiple PRECARIOUS PARENS

WHAT the hell is PRECARIOUS PARENS

  A close-paren that would move if the cursor was removed

Let's call this aforementioned EXPRESSION with PRECARIOUS PARENS a PRECARIOUS EXPRESSION?
                                                                   ---------------------

Does moving the cursor anywhere inside a precarious expression require full
resolution? YES?

PROVE IT

WAIT what are we trying to get to again?
Trying to see if we ever have to run a partial correction.
If we never have to run a partial correction, then maybe we can just run Paren Mode
when an expression leaves a state of tension.

RANDOM QUESTION:
(foo |(
^
moving the cursor before this paren should release a previously precarious expression?
conjecture: yes

RANDOM QUESTION: can I just run Paren Mode when a cursor releases a precarious expression?
conjecture: yes

RANDOM QUESTION: can there ever be more than one precarious expression?
conjecture: no
(as long as there is only one cursor)

CONJECTURE: moving the cursor outside of the HOLDING area:
- does not create another PRECARIOUS expression
- thus can be resolved by running Paren Mode on whole file

(foo |(
 ^^^^ HOLDING area of a precarious expression

HOW THEN do I detect the release of a precarious expression?
- previousCursor was in a holding area
- cursor is not in a holding area

DESIGN DRAFT for Smart Mode dedent resolution:
- if `changes` NOT passed and `previousCursor` IS passed
- detect release of precarious expression (using aforementioned steps)
- if detected, exit and run Paren Mode
shaunlebron commented 7 years ago

implemented the current solution here: https://github.com/shaunlebron/parinfer/commit/24e31bbdbf792dbd15bc907c9887159cae13ba46

closing for now