How to determine if a document change is as a result of an editor action?

I'm trying to integrate Parinfer (http://shaunlebron.github.io/parinfer/) into my plugin. Basically I have to update the document for an editor with a transformation of the original text based on document updates. I'm using a document listener for this, but I can't find a way to figure out if the change is as a result of user action in an editor or not. Is there a way to do this? What I need is the caret position for the editor which provoked the change, if any. I can maintain a list of all editors open on a document and modify the algorithm to take all carets into account, but I thought I'd check if there was a better way to do this first.

9 comments
Comment actions Permalink

We've implemented Parinfer in a few other editors (e.g. Vim, Sublime, Atom).  Would be nice to get help on this.  Thanks!

0
Comment actions Permalink

Maybe an action listener can help you (see ActionManager#addAnActionListener).

0
Comment actions Permalink

Thanks for the quick answer. I'm not quite sure how I'd use that, though. I guess the listeners are synchronous, so I could get the editor from the action and store it somewhere (user data on the document, for example) if the action relates to a relevant editor. Then in the document listener get the stored editor and use it if it exists? Something like that?

0
Comment actions Permalink

I thought the main problem was to find out whether document change is caused by action, or e.g. by synchronizing with file system (in case of update from VCS, etc). If you just need to find the active editor for the document, EditorFactory#getEditors and FileEditorManager#getSelectedTextEditor can probably help.

I'm not acquainted with Parinfer, so I'm not sure what exactly you want to do, but maybe another option is to enhance specific editor actions explicitly, by registering EditorActionHandler-s for them.

0
Comment actions Permalink

Well, ideally for a document change I'd be able to tell if that change was a result of user action in an editor, and if so which editor it was. I can't see how to do that, but getting the list of open editors for a particular document is an acceptable workaround - thanks.

0
Comment actions Permalink

So I have a basic implementation working now. I have some problems, on the IntelliJ side they're mainly around undo.

Parinfer is triggered by a document listener. On a document modification, I set an alarm for 10ms, and if more document events happen I reset the alarm. This means that parinfer runs 10ms after the last modification to the document. Parinfer will then modify the document and correct either parenthesis location or indentation. I have a flag so that these modifications don't trigger further iterations of parinfer.

The problem I have now is around undo. If I perform these updates in a Command, if the user executes undo after a parinfer modification, only the parinfer change will be undone. This will confuse the user and also leave the document in a state which doesn't comply with the parinfer invariants. Is there any way I can create a command linked to the previous command, so an undo action would undo two of them? Or can I add more modifications to an existing command?

I'd also like to know if I can execute arbitrary code on an undo. When the user selects parinfer mode, the document has to have its indentation corrected so that it complies with the parinfer invariants. If the user realises that that's not what they wanted, it would be good if the undo could also switch out of parinfer mode back to whichever the previous one was. Is this possible?

Finally, the reference implementation of parinfer is written as a pure function, that takes a whole document and returns a whole new one. I was planning to modify it so that I record the individual modifications and then apply them rather than a wholesale replace of the whole doc. Which is likely to be more efficient? I'd expect a common parinfer run to produce 3-6 small document modifications on average.

0
Comment actions Permalink

> Is there any way I can create a command linked to the previous command, so an undo action would undo two of them? Or can I add more modifications to an existing command?

Use CommandProcessor.runUndoTransparentAction instead of CommandProcessor.executeCommand.

> I'd also like to know if I can execute arbitrary code on an undo.

You can probably do that in a command listener by checking UndoManager.isUndoInProgress. But modifying the document after undo will reset undo stack and redo won't be possible.

> I was planning to modify it so that I record the individual modifications and then apply them rather than a wholesale replace of the whole doc. Which is likely to be more efficient?

In general, a couple of small changes scattered around the document will be more efficient than equivalent whole-text replacement. But replacement code detects a case when a replacement actually affects only a part of the target string, so if your small changes are very close to each other in the document, your current approach can probably be even more efficient (unless the document is very large and scanning it all to find the changed fragment will take a lot of time).

0
Comment actions Permalink

Thanks for the quick response, Dmitry!

> use CommandProcessor.runUndoTransparentAction instead of CommandProcessor.executeCommand.

What does this actually do? Does it merge the changes from the transparent action with the previous undoable command?

> You can probably do that in a command listener by checking UndoManager.isUndoInProgress.

Ok, so when undoing, the undo itself is also run as a command?

> But modifying the document after undo will reset undo stack and redo won't be possible.

That's ok, the code I need to run won't modify the document.

Thanks for the information about document replace too, that's very interesting.

0
Comment actions Permalink

> > use CommandProcessor.runUndoTransparentAction instead of CommandProcessor.executeCommand.

> What does this actually do? Does it merge the changes from the transparent action with the previous undoable command?

Yes.

> Ok, so when undoing, the undo itself is also run as a command?

Yes, you can see the details in UndoManagerImpl implementation.

0

Please sign in to leave a comment.