-
Notifications
You must be signed in to change notification settings - Fork 405
File patch selection and context menu interaction #853
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks great; nice job tracking this down, and 👍 for a nice elegant solution.
Some questions below, but otherwise 🚢
@@ -71,6 +72,7 @@ export default class GithubPackage { | |||
this.setActiveContext(WorkdirContext.absent()); | |||
} | |||
}), | |||
ContextMenuInterceptor, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure I understand why this is necessary, if registrations only happen on mount and are removed on unmount.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ContextMenuInterceptor
uses a single native event listener attached to the document
that's shared among all instances. Each instance registers and deregisters an element in a Map, but the event listener itself isn't removed until the static ContextMenuInterceptor.dispose()
method is called. This bit ☝️ ensures that we don't leak listeners on package deactivation.
... Although, this might be overkill. I could just register a new document listener for each instance and check element.includes(event.target)
? I was intending to minimize the number of listeners we register but maybe a single listener that does an O(n) operation on a Map isn't any better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave the simplification here a shot and our test suite leaked a ton of event listeners, because Enzyme doesn't trigger componentWillUnmount
. I think I'm just going to leave it as -is with the static Map.
lib/views/hunk-view.js
Outdated
className={`github-HunkView-line ${lineSelectedClass} is-${line.getStatus()}`} | ||
onMouseDown={event => this.props.mousedown(event, line)} | ||
onMouseMove={event => this.props.mousemove(event, line)} | ||
onContextMenu={event => this.props.contextMenuOnItem(event, line)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure so thought I'd ask — can this be removed now that ContextMenuInterceptor
wraps it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 🔥 Totally.
Strictly speaking, the "actual fix" for #822 is a missing
event.persist()
call. InFilePatchView.contextMenuOnItem()
, the MouseEvent is used in theresend
callback, which is passed tosetState()
and may or may not be invoked asynchronously depending on React's scheduling mechanism. If you reproduce in dev mode you'll see a bunch of warnings to that effect in the console.However... while I was reproducing this, I discovered that the right-click behavior no longer matched what the code was intended to do. We were intending to add the right-clicked line or hunk to the selection if it was not already selected, but what was actually happening was that the context menu was appearing before the selection was modified.
This likely regressed in #617, because React's synthetic event system relies on event handlers added to the
document
, but Atom implements context menus internally by adding an event handler todocument
first. React's synthetic event callback fires too late to cancel the event.To fix this, I've introduced a ContextMenuInterceptor component that installs a single native handler on the document with
{capture: true}
so that it'll trigger before Atom's handler.I also had to change the event re-fire from a
requestAnimationFrame
to asetImmediate
to actually allow the repaint with the updated selection to occur before the context menu is shown. I'm not entirely sure why that happens, but I suspect it has to do with this bit from the documentation:If I'm reading that right I bet two nested requestAnimationFrame() calls would do the trick as well... but setImmediate() works reliably for me and is somewhat cleaner.