Removing subscriptions with panel button


#1

I have a simple panel that displays a button. In my main file I subscribe to events like so:

@subscriptions = new CompositeDisposable
@subscriptions.add atom.workspace.observeTextEditors (editor) =>
  buffer = editor.getBuffer()
  buffer.stoppedChangingDelay = 5000
  bufferChangedSubscription = buffer.onDidStopChanging =>
    buffer.transact =>
      @someMethod()

I would like to dispose of these subscriptions and stop listening for buffer.onDidStopChanging. Furthermore I would like to detect if theses subscriptions already exist in case I run the same command twice by accident. Every time I call the same command and check if @subscriptions? exists I get undefined even though the events are still listening. In other words, @someMethod() gets called twice in a row if I subscribe twice. How can I detect if there exists subscriptions already? And how can I have a handle on the subscription linked to a button that disposes of them?


#2

Well, if I’m understanding correctly, you have two subscriptions that you want to dispose of. You have the observeTextEditors subcription and the onDidStopChanging subscription. But from the code above, you’re only adding one of them to the @subscriptions collection. The onDidStopChanging subscription you’re assigning to bufferChangedSubscription which, if it is a variable local only to the observeTextEditors callback, will not be retained anywhere except within that closure. Essentially, you lost the handle to dispose of it there. So yes, I can easily see this happening …

  1. You open a file
  2. You call the code above
  3. You call @subscriptions.dispose()
  4. You call the code above again

You now have two onDidStopChanging subscriptions, neither of which you can dispose of.

Additionally, setting the buffer.stoppedChangingDelay is manipulating an undocumented and probably intentionally private value that code other than your own may be depending on to be set to the normal value. This is generally bad form because while your code may work as intended … other people’s code may now not when installed alongside your package.


#3

@leedohm: I am unclear how the subscriptions and emitters work. My question should really have been different. I have been looking at the Emitter documentation and CompositeDisposable. What I really am trying to achieve is in my main file I would like to call someMethod only once if I would like. I also want to be able to watch for changes in the file being edited and automatically call someMethod if changed. The above code does indeed achieve this, but I would like to indicate in a panel that the automated calling of someMethod is active and a toggle button that unsubscribes from watching the buffer. I am not sure what is the best way to achieve this. I have been looking at code from other packages but I can’t seem to get a MWE that would do something like this. The main file can be seen here, and the view is here. Where am I going wrong?

Also, if I would like to change the delay time in watching for changes is there a way to do this without effecting other packages?


#4

I’m probably not making this very simple to understand … I’m kind of typing this up while waiting for some friends to join me in playing Destiny :laughing: Maybe @Trudko has something written up on subscriptions and emitters on his Atom Tips blog? (If not, maybe they should :wink:)

First, you want to watch for changes in the file being edited. There are probably a few different ways to do this, but it seems that you just want to watch for changes in the active file? You don’t need to be notified of changes in files that aren’t being actively edited? If so, I’m thinking something like this:

  1. Subscribe to atom.workspace.onDidChangeActivePaneItem to be notified when the active pane item is changed
  2. When the active pane item changes
    1. Call @stopChangingSubscription?.dispose() and @stopChangingSubscription = null
    2. If the active pane item is a TextEditor, subscribe to the onDidStopChanging event and save the Disposable in @stopChangingSubscription

Then if you have a button somewhere, it should just execute:

@stopChangingSubscription?.dispose()
@stopChangingSubscription = null

when it is clicked.

As for changing the delay time, what you should probably do is roll your own onDidStopChanging support … by subscribing to the onDidChange event instead and just call setTimeout with a callback after your custom timeout length, similar to the code in TextBuffer here:


#5

@leedohm: Thank you. This should give me a better starting point. When you say the button can call @stopChangingSubscription, how do I expose @stopChangingSubscription to the view. The main file handles the subscriptions and the view is in a different file. If I call @stopChangingSubscription in the view, it will complain about it being undefined no? This is probably a more fundamentals question, forgive me, this is my first project with coffeescript.


#6

No problem :grinning:

  1. You could expose a public function (say getChangingSubscription) on the main file
  2. You can require the main file in the view
  3. Call MainFile.getChangingSubscription from the view

You could create an API of sorts in the main file like this.

This is, of course, only one possible implementation. Another would be to have the view own the subscription instead.


#7

@leedohm: Ahhh. I left out the key part: require it. Thanks for the help!


#8

@leedohm: Hello. I had a chance to try some things out. This is what I have:

@subscriptions.add atom.workspace.observeTextEditors (editor) =>
  buffer = editor.getBuffer()
  buffer.onDidChange =>
    setTimeout(@compile.bind(this), 5000) 

Problem is that this still gets called on every change, except now the callback is delayed by 5 seconds. I am not sure this is what you had in mind. To clarify, i am trying to compile a file automatically 5 seconds after any file in the project was changed. There should be a standard way to set this delay.


#9

No, that isn’t exactly what I had in mind. The event you’re trying to emulate is onDidStopChanging … and as you’ve noticed, you’re just delaying “changed” by five seconds. So what you have to do is if onDidChange gets called before the timeout happens, you need to cancel it and set the timeout again. For example:

  1. I make a change, setTimeout gets called
  2. 2 seconds later I make another change, cancel timeout from step #1 and call setTimeout again
  3. 3 seconds after #2 I make another change, cancel timeout from step #2 and call setTimeout again
  4. etc
  5. Finally I stop editing and five seconds pass
  6. Last setTimeout callback gets fired

#10

@leedohm: I see. I guess I should have seen that in the code you referred me to. :smile: Thanks for the help.


#11

@leedohm thanks, I added it to my idea list :slight_smile: