-
Notifications
You must be signed in to change notification settings - Fork 7.5k
Use Tern jump-to-definition search for quickeditt #3847
Changes from 2 commits
6c36599
7a86aad
706d72d
2bcdc3a
8fbbeed
59587ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -126,13 +126,13 @@ define(function (require, exports, module) { | |
| this.$editorsDiv.on("mousewheel.MultiRangeInlineEditor", function (e) { | ||
| e.stopPropagation(); | ||
| }); | ||
|
|
||
| // Outer container for border-left and scrolling | ||
| this.$relatedContainer = $(window.document.createElement("div")).addClass("related-container"); | ||
|
|
||
| // List "selection" highlight | ||
| this.$selectedMarker = $(window.document.createElement("div")).appendTo(this.$relatedContainer).addClass("selection"); | ||
|
|
||
| // Inner container | ||
| this.$related = $(window.document.createElement("div")).appendTo(this.$relatedContainer).addClass("related"); | ||
|
|
||
|
|
@@ -165,8 +165,11 @@ define(function (require, exports, module) { | |
| // select the first range | ||
| self.setSelectedIndex(0); | ||
|
|
||
| // attach to main container | ||
| this.$htmlContent.append(this.$relatedContainer).append(this.$editorsDiv); | ||
| if (this._ranges.length > 1) { // attach to main container | ||
| this.$htmlContent.append(this.$relatedContainer).append(this.$editorsDiv); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to just break out the if (this._ranges.length > 1) {
this.$htmlContent.append(this.$relatedContainer);
}
this.$htmlContent.append(this.$editorsDiv);(That might look like it's semantically different, but because of the way jQuery chaining works, a chain of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. |
||
| } else { // no need to show ranges found if there's only one | ||
| this.$htmlContent.append(this.$editorsDiv); | ||
| } | ||
|
|
||
| // Listen for clicks directly on us, so we can set focus back to the editor | ||
| var clickHandler = this._onClick.bind(this); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -449,9 +449,8 @@ define(function (require, exports, module) { | |
|
|
||
| var $deferredJump = getPendingRequest(file, offset, HintUtils.TERN_JUMPTODEF_MSG); | ||
|
|
||
| // pendingTernRequests[file] = null; | ||
|
|
||
| if ($deferredJump) { | ||
| response.fullPath = getResolvedPath(response.resultFile); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noticed one more thing--this addition causes a JSLint warning because JSLint wants functions to be defined before they're used. (I don't like that restriction, but it's basically because JSLint is a one-pass parser, I think, and for better or for worse we're requiring files to be JSLint-clean.) So
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No problem. Pushed just now. |
||
| $deferredJump.resolveWith(null, [response]); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ define(function (require, exports, module) { | |
| var MultiRangeInlineEditor = brackets.getModule("editor/MultiRangeInlineEditor").MultiRangeInlineEditor, | ||
| FileIndexManager = brackets.getModule("project/FileIndexManager"), | ||
| EditorManager = brackets.getModule("editor/EditorManager"), | ||
| DocumentManager = brackets.getModule("document/DocumentManager"), | ||
| JSUtils = brackets.getModule("language/JSUtils"), | ||
| PerfUtils = brackets.getModule("utils/PerfUtils"); | ||
|
|
||
|
|
@@ -105,24 +106,85 @@ define(function (require, exports, module) { | |
| function _createInlineEditor(hostEditor, functionName) { | ||
| var result = new $.Deferred(); | ||
| PerfUtils.markStart(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
|
|
||
| _findInProject(functionName).done(function (functions) { | ||
| if (functions && functions.length > 0) { | ||
| var jsInlineEditor = new MultiRangeInlineEditor(functions); | ||
| jsInlineEditor.load(hostEditor); | ||
|
|
||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.resolve(jsInlineEditor); | ||
| } else { | ||
| // No matching functions were found | ||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
|
|
||
| // Use Tern jump-to-definition helper, if it's available, to find InlineEditor target. | ||
| var helper = EditorManager.getQuickEditHelper(); | ||
| if (helper !== null) { | ||
| var response = helper(); | ||
| if (response.hasOwnProperty("promise")) { | ||
| response.promise.done(function (jumpResp) { | ||
| var resolvedPath = jumpResp.fullPath; | ||
| if (resolvedPath) { | ||
|
|
||
| // Tern doesn't always return entire function extent. | ||
| // Use QuickEdit search now that we know which file to look at. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was actually a little surprised to find that we're not using Tern to resolve to the actual definition when calling a function in a module where the exports are assigned separately from the definition (see #3863). I think your approach here will probably work for most cases (since most people would name the export after the original function anyway), but I wonder if we could leverage Tern to trace that somehow?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We asked Marijn to tweak Tern to provide this information, but didn't get what we needed. This way works, although it's a hack. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's leave this in for now, but keep #3863 open on doing it using Tern--if/when that gets fixed, we can take this out. |
||
| var fileInfos = []; | ||
| fileInfos.push({name: jumpResp.resultFile, fullPath: resolvedPath}); | ||
| JSUtils.findMatchingFunctions(functionName, fileInfos) | ||
| .done(function (functions) { | ||
| var jsInlineEditor = new MultiRangeInlineEditor(functions); | ||
| jsInlineEditor.load(hostEditor); | ||
|
|
||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.resolve(jsInlineEditor); | ||
| }) | ||
| .fail(function () { | ||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
| }); | ||
|
|
||
| } else { // no result from Tern. Fall back to _findInProject(). | ||
|
|
||
| if (!functionName) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like we should fail much earlier if this is the case :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (i.e., if we need to detect this, it should be at the top of the function.)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is will go back into the calling function (where it was originally), and I will remove it here. I had done what you see so that the Tern jump-to-definition part of the search might also find var names, but that turned out to have other problems. So, QuickEdit only finds function names, as it always has. |
||
| result.reject(); | ||
| } else { | ||
|
|
||
| _findInProject(functionName).done(function (functions) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should encapsulate both of the fallback cases (this one and the one on line 170 below) into a common function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It also feels like since the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, if we're willing to assume that there should always a helper function registered -- there should be -- we can just remove the second fallback and fail immediately if Jump-To-Definition isn't available. We'll still have the fallback if Tern doesn't find anything. On the whole, if we leave the second fallback in there, I don't thinks it's enough code to factor out into a separate function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine to remove the helper function fallback. Still would like to see the other two encapsulated if they're literally the same code (I just have a visceral reaction to duplication of more than a couple of lines :)) |
||
| if (functions && functions.length > 0) { | ||
| var jsInlineEditor = new MultiRangeInlineEditor(functions); | ||
| jsInlineEditor.load(hostEditor); | ||
|
|
||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.resolve(jsInlineEditor); | ||
| } else { | ||
| // No matching functions were found | ||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
| } | ||
| }).fail(function () { | ||
| PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| }).fail(function () { | ||
| PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
| }); | ||
|
|
||
| } | ||
| }).fail(function () { | ||
| PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
| }); | ||
|
|
||
|
|
||
| } else { | ||
|
|
||
| _findInProject(functionName).done(function (functions) { | ||
| if (functions && functions.length > 0) { | ||
| var jsInlineEditor = new MultiRangeInlineEditor(functions); | ||
| jsInlineEditor.load(hostEditor); | ||
|
|
||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.resolve(jsInlineEditor); | ||
| } else { | ||
| // No matching functions were found | ||
| PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
| } | ||
| }).fail(function () { | ||
| PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); | ||
| result.reject(); | ||
| }); | ||
| } | ||
|
|
||
| return result.promise(); | ||
| } | ||
|
|
||
|
|
@@ -147,14 +209,15 @@ define(function (require, exports, module) { | |
| if (sel.start.line !== sel.end.line) { | ||
| return null; | ||
| } | ||
|
|
||
| // Always use the selection start for determining the function name. The pos | ||
| // parameter is usually the selection end. | ||
| var functionName = _getFunctionName(hostEditor, sel.start); | ||
|
|
||
| if (!functionName) { | ||
| return null; | ||
| } | ||
|
|
||
| return _createInlineEditor(hostEditor, functionName); | ||
| } | ||
|
|
||
|
|
||
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 guessing that the reason you're doing this is because there isn't a way for one extension (JavaScriptQuickEdit) to call directly into another extension (JavaScriptCodeHints), which is an unfortunate limitation of our current extensibility mechanism. But I'm leery of adding public functionality like this into a core class like Editor, especially since the way this is set up, it won't generalize at all (e.g., if we had some other kind of "jump to definition" functionality for other languages in the future--e.g. going from an ID in a CSS selector to the HTML tag with that ID).
So maybe what we should do is make "Jump to Definition" actually be a command that's defined in the core, and have it rely on some
findDefinition()function; that function in turn would delegate to providers that can be plugged in by language-specific extensions (where each provider would look at the current editor's mode to figure out if it applied--similar to how we do other language-specific providers).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.
Yes, this is a hack to let two extensions, QuickEdit and CodeHints, communicate. The most general solution would be to implement a registration and lookup mechanism for extensions that would allow them all to call each other. I don't think moving Jump-To-Definition to the core is as good. The other possibility is to move Tern to the core and allow extensions to call it directly.
[ Also sent this comment by email to the DL-Brackets Dev mailing list. ]
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.
We chatted about it at the scrum this morning, and came up with two suggested alternatives:
Given that we're near the end of the sprint, (1) is fine for now since it's less work/less risk--we can file a bug to do (2) later on.
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.
Agreed. Option #1 is equivalent to what I've done. If you can be more explicit about exactly what you'd like here, I'll make that change today.
Option #2 will solve our immediate problem, but won't deal with general communication between arbitrary extensions. We should consider what we really want in the long term.