Fork me on GitHub
#parinfer
<
2016-02-16
>
chrisoakman00:02:29

"implies that indent mode should be run again on a caret movement where the caret leaves the line with the held paren" <-- this is correct and expected behavior while in Indent Mode

chrisoakman00:02:57

@cfleming: have you used atom-parinfer at all? it might be worth using it some to get a feel for how Parinfer works there

chrisoakman00:02:57

the code for applying Paren Mode when a file is opened can get tricky. For reference: https://github.com/oakmac/atom-parinfer/blob/master/src-cljs/atom_parinfer/core.cljs#L337-L383

cfleming00:02:36

@chrisoakman: I haven’t but I’ve been meaning to

chrisoakman00:02:06

might be helpful; atom-parinfer is the most mature and stable implementation of parinfer (to my knowledge)

cfleming00:02:15

> this is correct and expected behavior while in Indent Mode Doesn’t this mean that you can never actually manually enter a close paren though?

chrisoakman00:02:58

while in Indent mode, closing parens at the end of lines are always "under control" of the Parinfer algorithm

chrisoakman00:02:22

you can edit them, but then Parinfer will always just replace what you type

cfleming00:02:06

Right. The more I play around with it, the more I think a hybrid mode is what’s required.

chrisoakman00:02:28

give atom-parinfer a try

cfleming00:02:31

I have to get ready for a demo, I’ll be back later on.

cfleming00:02:40

I'll try it out this afternoon.

cfleming01:02:16

@chrisoakman: I can’t find the parinfer package for Atom

chrisoakman01:02:52

are you on the packages tab in the settings?

cfleming01:02:52

From the getting started page, I clicked “Install a package” and searched for “parinfer”, but it doesn’t find anything.

chrisoakman01:02:16

which platform are you on?

chrisoakman01:02:43

then the "Install" tab

chrisoakman01:02:07

then type "parinfer" + Enter

cfleming01:02:27

There it is - I think the first time it presented me with a list of recommended plugins

cfleming01:02:34

I’d swear I did that simple_smile

chrisoakman01:02:45

or if you were on the "Packages" tab, that was a list of your existing packages

cfleming01:02:52

That might have been it.

cfleming01:02:01

Anything else I need for Clojure?

chrisoakman01:02:16

I think it ships with the Clojure language already

chrisoakman01:02:22

nope - give that a try

cfleming01:02:26

Ok, I’ll play around

chrisoakman01:02:27

pretend like you're a new Clojure user 😉

chrisoakman01:02:45

Are you aware of the Cmd + ( and Cmd + ) hotkeys?

cfleming01:02:36

Ok, swaps and disables, looks like

cfleming01:02:55

Having used this more than me, what’s your feeling about the usefulness of paren mode?

chrisoakman01:02:23

as an editing mode? I basically never use it

cfleming01:02:43

Do you think it would be ok to only implement indent mode in Cursive?

chrisoakman01:02:45

I can't remember the last time I ever turned it on other than checking the extension

cfleming01:02:49

For editing?

chrisoakman01:02:09

are you planning on having a toggle to turn Parinfer on / off?

chrisoakman01:02:26

then I would offer it

chrisoakman01:02:48

I mean - I'm not familiar with Cursive conventions

chrisoakman01:02:21

but I think if you copy the atom-parinfer conventions of Ctrl + ( and Ctrl + ) that would be good

chrisoakman01:02:41

after using Parinfer for a while, you don't even notice it's there

chrisoakman01:02:51

it automatically turns on for file extensions it recognizes

chrisoakman01:02:01

runs paren mode (which usually doesn't do anything to the file)

chrisoakman01:02:05

and drop you into indent mode

chrisoakman01:02:15

it's only when you first start using it that you hit the Paren Mode edge cases

chrisoakman01:02:30

also - none of the extensions are using cursorDx correctly right now

chrisoakman01:02:50

so that limits Paren Mode's usefulness

cfleming01:02:03

That’s what I’ve been thinking. I’m also thinking that I might start working towards the hybrid mode by special casing some things (like entering a closing paren)

cfleming01:02:35

I think people will expect that case to correct indentation rather than parens.

cfleming01:02:58

So you also run parinfer on cursor movement?

chrisoakman01:02:57

I would suggest the opposite, actually

chrisoakman01:02:05

re: entering closing parens

chrisoakman01:02:17

when using Indent Mode, indentation is the master and parens are the slave

cfleming01:02:38

But then it doesn’t make sense to even allow them to enter a closing paren

chrisoakman01:02:49

you mean at the end of a line?

chrisoakman01:02:05

I mean - it just doesn't matter if they do or not

chrisoakman01:02:22

if they do, and it's the "correct" one, then parinfer runs and it looks like nothing happens

chrisoakman01:02:32

if they do, and it's the "wrong" one, parinfer runs and corrects it immediately

cfleming01:02:38

No it doesn't

cfleming01:02:52

(let [x 10]<caret>
  (when true))

cfleming01:02:05

If you close the paren there, the paren stays until you move off the line.

chrisoakman01:02:17

due to the cursor rules

cfleming01:02:21

Here’s another one:

cfleming01:02:41

(let<caret> [x 10]
  (when true))

cfleming01:02:05

If you close that paren, it doesn’t get corrected but the indentation is wrong on the (when true)

chrisoakman01:02:37

that's just how parinfer works

cfleming01:02:51

I just don’t think it’s ever valid to close a paren and not correct indentation

chrisoakman01:02:11

indent mode never touches indentation

cfleming01:02:16

I mean, I understand the theory but using it it would feel more natural if it did correct indentation.

cfleming01:02:29

I know, I’m arguing that that’s not a good invariant to enforce simple_smile

cfleming01:02:55

I’m basically arguing to move towards the hybrid mode in #86

chrisoakman01:02:15

I need to give it a think; it's hard for me to separate my thoughts on this since I have ported the thing 4 times and written two plugins for it

cfleming01:02:30

Sure, let me know

chrisoakman01:02:31

it feels very natural to me at this point

cfleming01:02:38

As a new user, those cases feel wrong

cfleming01:02:53

Maybe they never actually come up in practice

chrisoakman01:02:01

when in indent mode, my primary "control lever" is indentation, basically tab and shift + tab

chrisoakman01:02:21

just like it would be in Python or if I were formatting JS code to common convention

cfleming01:02:02

Sure, but I don’t think that precludes other types of manipulations doing intuitive things.

chrisoakman01:02:05

and I practically never leave Indent Mode

cfleming01:02:10

Here’s another case:

cfleming01:02:24

(<caret>let [x 10]
  (when true))

cfleming01:02:38

If you insert an opening paren there, the when is not indented further.

chrisoakman01:02:20

I would have to use it with those modifications in order to see if they felt "natural"

chrisoakman01:02:37

of course "natural" is a function of what you're familiar with... but you know what I mean

cfleming01:02:50

Sure, I need to think about what the cases are and when this would make sense

cfleming01:02:03

I’ll add these cases to #86

chrisoakman01:02:10

would also be interested to hear what Shaun thinks about it

chrisoakman01:02:26

he's good at coming up with use cases that break or support intended behavior

cfleming01:02:28

But I’m leaning towards implementing a hybrid mode out of the gate and iterating based on feedback.

chrisoakman01:02:16

that might be quite tricky to implement; I'm not sure

chrisoakman01:02:47

something about the parinfer algorithm right now: it doesn't know what you just typed

chrisoakman01:02:20

it just has input text --> output text; the result is a pure function of the input text

chrisoakman01:02:30

(with optional cursor parameters, but still makes it a pure function)

chrisoakman01:02:56

so you would have to come up with "what the user just typed"

cfleming01:02:06

I have that

chrisoakman01:02:07

which you might know in some simple cases, like regular typing

chrisoakman01:02:24

but that becomes quite tricky once you start dealing with copy/paste and multiple cursors, etc

cfleming01:02:26

Or at least, for a document change event, I have the old text and the new text

cfleming01:02:30

I think that what I would do is: the before or after text contain an unbalanced number of parens, I would adjust the indentation for the whole top-level form.

cfleming01:02:55

Everything else can be indent mode I think.

cfleming01:02:47

Basically, if the event is changing the nesting, then the indentation needs to be adjusted

chrisoakman01:02:25

I'm not sure what happens at the end of a line when you do that

chrisoakman01:02:59

yeah; interesting

chrisoakman01:02:11

having some test cases and discussing on Issue #86 is the way to go I think

chrisoakman01:02:35

being able to test it out would be helpful too

cfleming01:02:10

Yeah, I’ll try to modify the algorithm to test it out, and I can help you get a test build of Cursive installed to try when I do.

chrisoakman01:02:11

try some real-world editing in Atom for a day; see how it feels after a while

chrisoakman01:02:59

sure; I would be open to that

cfleming01:02:26

So do you run parinfer on every caret movement as well as doc changes?

chrisoakman01:02:30

every onChange and cursorMove event

chrisoakman01:02:40

which is why the debounce is important, because those fire all the time

cfleming01:02:55

Right. I’m using 10ms which seems good.

chrisoakman01:02:02

on a single keypress both of those events are fired

cfleming01:02:03

It could probably be longer.

chrisoakman01:02:11

it's probably fine

chrisoakman01:02:18

parinfer is so fast

chrisoakman01:02:30

unless you're noticing any sluggishness

cfleming01:02:54

No, but in IntelliJ doc changes trigger a whole lot of stuff, it’s not just about the speed of parinfer

chrisoakman01:02:30

I'm not sure I know what you mean when you say "doc change"

cfleming01:02:50

When the edited document is updated with the changes from parinfer.

cfleming02:02:02

(or anything else)

cfleming02:02:31

@shaunlebron: Actually, re-reading the issue about caret movement I misunderstood what was written there - feel free to ignore me simple_smile

shaunlebron04:02:46

trying to distill some of the points you made so I can address them simply

shaunlebron04:02:18

1. user should not be able to type a close-paren in Indent Mode (given the current rules)

shaunlebron04:02:56

2. the behavior of “holding parens” is confusing and breaks Indent Mode rules

shaunlebron04:02:07

3. Indentation can be wrong when inserting a close-paren

shaunlebron04:02:29

4. Inserting close-parens should trigger an indentation correction

shaunlebron04:02:24

For point 1, here’s an example of a simple close-paren insertion that doesn’t cause indentation problems, and it results in an inline paredit-barf

shaunlebron04:02:08

(def foo| bar) => (def foo|) bar, after inserting a ) at the cursor

shaunlebron04:02:43

For point 2, let me sketch out a diagram...

shaunlebron05:02:40

The problem is that indent mode does not allow you to move between these states

shaunlebron05:02:56

because the moment you type the ], it would be displaced

shaunlebron05:02:54

the problem is that moving between valid states sometimes requires moving through an invalid one

shaunlebron05:02:45

I’m not saying this is the best way, this is just to paint a picture of the model that I currently use to solve it

shaunlebron05:02:44

You may be wondering why “State 2” is valid above. Obviously, the indentation is wonky. This brings us to Point 3. I’ll first address the example you gave for clarity, and then cover “State 2"

shaunlebron05:02:27

Cursor is |

(let| [x 10]
  (when true))

shaunlebron05:02:22

when the user types a ), we get

(let)| [x 10]
  (when true)

shaunlebron05:02:58

Even when the cursor moves away, this indentation here will remain the same:

(let) [x 10]
  (when true)

shaunlebron05:02:08

So we end up with code that has wonky indentation, just like “State 2” in the previous example

shaunlebron05:02:20

This is just a design decision that was made

shaunlebron05:02:30

even though the indentation is weird, it doesn’t violate the indentation thresholds

shaunlebron05:02:56

The right-bound of the threshold is this:

(let) [x 10]
(when true)

shaunlebron05:02:57

You can indent the expression to the right, but the structure won’t change until you hit this:

(let) [x 10
       (when true)]

shaunlebron05:02:32

And since Parinfer respects the user’s chosen indentation within thresholds, we allow this:

(let) [x 10]
  (when true)

shaunlebron05:02:35

I think my guiding design philosophy behind these examples is that I find it important to allow this motion between valid states

shaunlebron05:02:22

Alright, for Point 4, I think this will sum up my thoughts pretty well...

shaunlebron05:02:31

My suspicion is that Parinfer has become a set of primitives by which you can perform what you describe in Point 4

shaunlebron05:02:40

you can see this by how Mike Fikes has combined the modes to meet his own needs in Planck

shaunlebron05:02:08

For example, for point 4, we start with:

(let [x 1]|
  (when true))

shaunlebron05:02:50

In indent mode, we insert a ):

(let [x 1])|
  (when true)

shaunlebron05:02:31

If you wish indentation to be corrected, you don’t have to rewrite Parinfer. Rather, you can treat Parinfer as the primitive operations which you can compose for your desired behavior. To continue the example, you can have Cursive’s plugin apply Paren Mode when ) is typed:

(let [x 1])
(when true)

shaunlebron05:02:28

I’m really happy that you’re challenging the notions of what Parinfer should be doing in different cases

shaunlebron06:02:08

I would recommend thinking about your custom rules not as a new mode separate from Indent Mode and Paren Mode

shaunlebron06:02:00

but rather as a set of rules for determining when to apply each Mode

shaunlebron06:02:23

anyway, I’ve tried to get everything that I know about Parinfer into the website, design doc, and test cases for posterity.

shaunlebron06:02:37

but I still think the best way to explore an idea is to ask a person, so keep the questions coming if you have a question

cfleming09:02:47

Thanks for the thoughtful feedback @shaunlebron, this is very interesting.

cfleming09:02:08

I agree that this should be thought of as a combination of primitives. I think I’m proposing to modify paren mode to use IntelliJ/Cursive’s indentation, so it will use semantically-aware indentation. This will make it probably too slow to be as real-time as indent mode, but even with these modifications it won’t be run that often.

cfleming09:02:15

I need to try it though.

cfleming09:02:54

And then to work out when that should be applied based on the change to the document.

thomas09:02:01

Hi, can I just say that I LOVE parinfer!!!! I am using it with Atom and it works perfect!!!

edpaget16:02:20

Hey all after seeing parinferlib in emacslisp I updated my attempt at the parinfer emacs plugin to use it: https://github.com/edpaget/parinfer-mode

edpaget16:02:45

I think it's working better than the node version did, but I'm not sure it's all the way correct yet, if someone wants to give it a try I'd appreciate it

dominicm16:02:06

That's great simple_smile

edpaget16:02:32

unfortunately for me, today is a python day at work instead of a clojure one, so I'll have to try it more after work

chrisoakman16:02:59

let me know if you need anything from the parinferlib side; I'm looking at MELPA right now

edpaget18:02:39

@chrisoakman: thanks for porting it over. I gave it a try about a month ago and got totally overwhelmed.

edpaget18:02:33

I think all you'll need to do to add it to melpa is add some metadata to the main parinferlib file and send a pull request here: https://github.com/melpa/melpa

edpaget18:02:51

I can look into exactly what need to be added later

chrisoakman18:02:02

right - I was reading about that

chrisoakman18:02:44

I was thinking it definitely makes sense for the editing mode to be available from a package manager like that; does the same logic apply for just the library though?

chrisoakman18:02:05

would your package just depend on the parinferlib package?

chrisoakman18:02:13

and people's Emacs installations would figure out that dependency chain?

chrisoakman18:02:17

I know nothing about Emacs

edpaget18:02:11

haha, yeah packages can declare dependencies and melpa (or package.el can't remember which bit handles it) will resolve them when they get installed

edpaget18:02:32

I could also see me just adding my little bit of code to your project and publishing that if you want

chrisoakman18:02:32

ok, so it's very similar to package.json and the npm world

edpaget18:02:46

since the hooks aren't very big at the moment

chrisoakman18:02:54

let's treat them separately; I'd like to keep the library independent

chrisoakman18:02:09

in case someone else wants to make a different Emacs package (for whatever reason)

edpaget18:02:22

sure sounds great to me

cfleming22:02:52

In the JVM parinfer, the PARENS map maps opening braces to closing ones, but it also maps closing ones to opening ones.

cfleming22:02:06

As far as I can tell, this is not required - am I missing something?

cfleming22:02:33

I can only see cases that I believe should map from openers to closers.

chrisoakman22:02:10

I think it uses both

chrisoakman22:02:27

but you could remove the closing -> opening map and see if all the tests still pass

cfleming22:02:49

Ok, I’ll try it

cfleming22:02:20

I’m modifying it at the moment to use Cursive’s lexer, which makes a lot of things simpler

cfleming22:02:50

Since I get strings, characters and comments as whole tokens, so a lot of the intermediate state tracking just goes away

cfleming22:02:33

But I also get #{ and #( as single tokens, so there’s no single mapping from a close token to an opening one