Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Initial Quick Docs implementation #3449

Merged
merged 7 commits into from
Apr 18, 2013
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/base-config/keyboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@
"navigate.toggleQuickEdit": [
"Ctrl-E"
],
"navigate.toggleQuickDocs": [
"Ctrl-K"
],
"navigate.previousMatch": [
{
"key": "Alt-Up",
Expand Down
1 change: 1 addition & 0 deletions src/command/Commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ define(function (require, exports, module) {
exports.NAVIGATE_GOTO_DEFINITION = "navigate.gotoDefinition";
exports.NAVIGATE_GOTO_LINE = "navigate.gotoLine";
exports.TOGGLE_QUICK_EDIT = "navigate.toggleQuickEdit";
exports.TOGGLE_QUICK_DOCS = "navigate.toggleQuickDocs";
exports.QUICK_EDIT_NEXT_MATCH = "navigate.nextMatch";
exports.QUICK_EDIT_PREV_MATCH = "navigate.previousMatch";

Expand Down
3 changes: 3 additions & 0 deletions src/command/DefaultMenus.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ define(function (require, exports, module) {
menu.addMenuItem(Commands.TOGGLE_QUICK_EDIT);
menu.addMenuItem(Commands.QUICK_EDIT_PREV_MATCH);
menu.addMenuItem(Commands.QUICK_EDIT_NEXT_MATCH);
menu.addMenuDivider();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally we ask extensions to add menus within the extension and not in DefaultMenus right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but this isn't an extension Command, is a new Quick Docs Command handled by the Editor Manager and the extension just adds a new Quick Docs provider that uses this command, just like Quick Edit works.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes. Thanks for catching my mistake. :)

menu.addMenuItem(Commands.TOGGLE_QUICK_DOCS);

/*
* Help menu
Expand Down Expand Up @@ -196,6 +198,7 @@ define(function (require, exports, module) {

var editor_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.EDITOR_MENU);
editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_EDIT);
editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_DOCS);
editor_cmenu.addMenuItem(Commands.EDIT_SELECT_ALL);

var inline_editor_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.INLINE_EDITOR_MENU);
Expand Down
6 changes: 3 additions & 3 deletions src/editor/CSSInlineEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ define(function (require, exports, module) {
}

// Only provide CSS editor if the selection is within a single line
var sel = hostEditor.getSelection(false);
var sel = hostEditor.getSelection();
if (sel.start.line !== sel.end.line) {
return null;
}

// Always use the selection start for determining selector name. The pos
// parameter is usually the selection end.
var selectorName = _getSelectorName(hostEditor, hostEditor.getSelection(false).start);
// parameter is usually the selection end.
var selectorName = _getSelectorName(hostEditor, sel.start);
if (selectorName === "") {
return null;
}
Expand Down
80 changes: 51 additions & 29 deletions src/editor/EditorManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ define(function (require, exports, module) {
*/
var _inlineEditProviders = [];

/**
* Registered inline documentation widget providers. See {@link #registerInlineDocsProvider()}.
* @type {Array.<function(...)>}
*/
var _inlineDocsProviders = [];

/**
* @private
* @param {?Editor} current
Expand Down Expand Up @@ -134,39 +140,41 @@ define(function (require, exports, module) {

/**
* @private
* Bound to Ctrl+E on outermost editors.
* @param {!Editor} editor the candidate host editor
* Finds an inline widget provider from the given list that can offer a widget for the current cursor
* position, and once the widget has been created inserts it into the editor.
* @param {!Editor} editor The host editor
* @param {!Array.<function(!Editor, !{line:Number, ch:Number}):?$.Promise>} providers
* @return {$.Promise} a promise that will be resolved when an InlineWidget
* is created or rejected when no inline editors are available.
* is created or rejected if no inline providers have offered one.
*/
function _openInlineWidget(editor) {
PerfUtils.markStart(PerfUtils.INLINE_EDITOR_OPEN);
function _openInlineWidget(editor, providers) {
PerfUtils.markStart(PerfUtils.INLINE_WIDGET_OPEN);

// Run through inline-editor providers until one responds
var pos = editor.getCursorPos(),
inlinePromise,
i,
result = new $.Deferred();

for (i = 0; i < _inlineEditProviders.length && !inlinePromise; i++) {
var provider = _inlineEditProviders[i];
for (i = 0; i < providers.length && !inlinePromise; i++) {
var provider = providers[i];
inlinePromise = provider(editor, pos);
}

// If one of them will provide a widget, show it inline once ready
if (inlinePromise) {
inlinePromise.done(function (inlineWidget) {
editor.addInlineWidget(pos, inlineWidget);
PerfUtils.addMeasurement(PerfUtils.INLINE_EDITOR_OPEN);
PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
result.resolve();
}).fail(function () {
// terminate timer that was started above
PerfUtils.finalizeMeasurement(PerfUtils.INLINE_EDITOR_OPEN);
PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
result.reject();
});
} else {
// terminate timer that was started above
PerfUtils.finalizeMeasurement(PerfUtils.INLINE_EDITOR_OPEN);
PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
result.reject();
}

Expand Down Expand Up @@ -197,21 +205,29 @@ define(function (require, exports, module) {
}

/**
* Registers a new inline provider. When _openInlineWidget() is called each registered inline
* widget is called and asked if it wants to provide an inline widget given the current cursor
* location and document.
* @param {function} provider
* Parameters:
* {!Editor} editor, {!{line:Number, ch:Number}} pos
*
* Returns:
* {$.Promise} a promise that will be resolved with an inlineWidget
* or null to indicate the provider doesn't create an editor in this case
* Registers a new inline editor provider. When Quick Edit is invoked each registered provider is
* asked if it wants to provide an inline editor given the current editor and cursor location.
*
* @param {function(!Editor, !{line:Number, ch:Number}):?$.Promise} provider
* The provider returns a promise that will be resolved with an InlineWidget, or returns null
* to indicate the provider doesn't want to respond to this case.
*/
function registerInlineEditProvider(provider) {
_inlineEditProviders.push(provider);
}

/**
* Registers a new inline docs provider. When Quick Docs is invoked each registered provider is
* asked if it wants to provide inline docs given the current editor and cursor location.
*
* @param {function(!Editor, !{line:Number, ch:Number}):?$.Promise} provider
* The provider returns a promise that will be resolved with an InlineWidget, or returns null
* to indicate the provider doesn't want to respond to this case.
*/
function registerInlineDocsProvider(provider) {
_inlineDocsProviders.push(provider);
}

/**
* @private
* Given a host editor, return a list of all Editors in all its open inline widgets. (Ignoring
Expand Down Expand Up @@ -649,28 +665,28 @@ define(function (require, exports, module) {


/**
* Toggle Quick Edit command handler
* @return {!Promise} A promise resolved with true if an inline editor
* is opened or false when closed. The promise is rejected if there
* is no current editor or an inline editor is not created.
* Closes any focused inline widget. Else, asynchronously asks providers to create one.
* @return {!Promise} A promise resolved with true if an inline widget is opened or false
* when closed. Rejected if there is neither an existing widget to close nor a provider
* willing to create a widget (or if no editor is open).
*/
function _toggleQuickEdit() {
function _toggleInlineWidget(providers) {
var result = new $.Deferred();

if (_currentEditor) {
var inlineWidget = getFocusedInlineWidget();

if (inlineWidget) {
// an inline widget's editor has focus, so close it
PerfUtils.markStart(PerfUtils.INLINE_EDITOR_CLOSE);
PerfUtils.markStart(PerfUtils.INLINE_WIDGET_CLOSE);
inlineWidget.close();
PerfUtils.addMeasurement(PerfUtils.INLINE_EDITOR_CLOSE);
PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_CLOSE);

// return a resolved promise to CommandManager
result.resolve(false);
} else {
// main editor has focus, so create an inline editor
_openInlineWidget(_currentEditor).done(function () {
_openInlineWidget(_currentEditor, providers).done(function () {
result.resolve(true);
}).fail(function () {
result.reject();
Expand All @@ -685,7 +701,12 @@ define(function (require, exports, module) {
}

// Initialize: command handlers
CommandManager.register(Strings.CMD_TOGGLE_QUICK_EDIT, Commands.TOGGLE_QUICK_EDIT, _toggleQuickEdit);
CommandManager.register(Strings.CMD_TOGGLE_QUICK_EDIT, Commands.TOGGLE_QUICK_EDIT, function () {
return _toggleInlineWidget(_inlineEditProviders);
});
CommandManager.register(Strings.CMD_TOGGLE_QUICK_DOCS, Commands.TOGGLE_QUICK_DOCS, function () {
return _toggleInlineWidget(_inlineDocsProviders);
});

// Initialize: register listeners
$(DocumentManager).on("currentDocumentChange", _onCurrentDocumentChange);
Expand Down Expand Up @@ -718,6 +739,7 @@ define(function (require, exports, module) {
exports.getFocusedInlineWidget = getFocusedInlineWidget;
exports.resizeEditor = resizeEditor;
exports.registerInlineEditProvider = registerInlineEditProvider;
exports.registerInlineDocsProvider = registerInlineDocsProvider;
exports.getInlineEditors = getInlineEditors;
exports.closeInlineWidget = closeInlineWidget;
});
4 changes: 2 additions & 2 deletions src/extensions/default/JavaScriptQuickEdit/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,14 @@ define(function (require, exports, module) {
}

// Only provide JavaScript editor if the selection is within a single line
var sel = hostEditor.getSelection(false);
var sel = hostEditor.getSelection();
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, hostEditor.getSelection().start);
var functionName = _getFunctionName(hostEditor, sel.start);
if (!functionName) {
return null;
}
Expand Down
19 changes: 19 additions & 0 deletions src/extensions/default/WebPlatformDocs/InlineDocsViewer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="css-prop-defn" tabIndex="0"> <!-- tabIndex needed: otherwise click focuses CodeMirror scroller -->
<h1>{{propName}}</h1>
<div class="css-prop-summary">
<h2>{{DOCS_SUMMARY}}</h2>
<div>{{{summary}}}</div>
</div>
<div class="css-prop-values">
<h2>{{DOCS_VALUES}}</h2>
<div>
{{#propValues}}
<dl>
<dt>{{value}}</dt>
<dd>{{{description}}}</dd>
</dl>
{{/propValues}}
</div>
</div>
<p class="more-info"><a href="{{url}}">{{DOCS_MORE_LINK}}</a></p>
</div>
112 changes: 112 additions & 0 deletions src/extensions/default/WebPlatformDocs/InlineDocsViewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/


/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $, window, Mustache */

/**
* Inline widget to display WebPlatformDocs JSON data nicely formatted
*/
define(function (require, exports, module) {
'use strict';

// Load Brackets modules
var InlineWidget = brackets.getModule("editor/InlineWidget").InlineWidget,
ExtensionUtils = brackets.getModule("utils/ExtensionUtils"),
Strings = brackets.getModule("strings"),
NativeApp = brackets.getModule("utils/NativeApp");

// Load template
var inlineEditorTemplate = require("text!InlineDocsViewer.html");

// Load CSS
ExtensionUtils.loadStyleSheet(module, "WebPlatformDocs.less");


/**
* @param {!string} cssPropName
* @param {!{SUMMARY:string, URL:string, VALUES:Array.<{TITLE:string, DESCRIPTION:string}>}} cssPropDetails
*/
function InlineDocsViewer(cssPropName, cssPropDetails) {
this.cssPropName = cssPropName;
this.cssPropDetails = cssPropDetails;
InlineWidget.call(this);
}

InlineDocsViewer.prototype = Object.create(InlineWidget.prototype);
InlineDocsViewer.prototype.constructor = InlineDocsViewer;
InlineDocsViewer.prototype.parentClass = InlineWidget.prototype;

InlineDocsViewer.prototype.cssPropName = null;
InlineDocsViewer.prototype.cssPropDefn = null;
InlineDocsViewer.prototype.$wrapperDiv = null;

InlineDocsViewer.prototype.load = function (hostEditor) {
InlineDocsViewer.prototype.parentClass.load.apply(this, arguments);

var propValues = this.cssPropDetails.VALUES.map(function (valueInfo) {
return { value: valueInfo.TITLE, description: valueInfo.DESCRIPTION };
});

var templateVars = $.extend({
propName: this.cssPropName,
summary: this.cssPropDetails.SUMMARY,
propValues: propValues,
url: this.cssPropDetails.URL
}, Strings);

var html = Mustache.render(inlineEditorTemplate, templateVars);

this.$wrapperDiv = $(html);
this.$htmlContent.append(this.$wrapperDiv);

this.$wrapperDiv.on("click", "a", function (event) {
event.preventDefault();
var url = $(event.target).attr("href");
if (url) {
// URLs in JSON data are relative
if (url.substr(0, 4) !== "http") {
url = "http://docs.webplatform.org" + (url.charAt(0) !== "/" ? "/" : "") + url.replace(" ", "_");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing spaces with underscores? Is that something we should cleanup in the JSON file? I also noticed a lot of unnecessary escaping of forward slashes.

One more thought on URLs. Here's an example from the length link in padding-bottom:

<p>Specifies a positive fixed distance. See <a href=\"http:\/\/docs.webplatform.org\/w\/index.php?title=css\/properties\/length&amp;action=edit&amp;redlink=1\" class=\"new\" title=\"css\/properties\/length (page does not exist)\">length<\/a>

The embedded HTML link specifies a title. Can we rewrite this to display the actual link instead? Otherwise, the user has no idea what the destination URL is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some searching in the JSON file and couldn't find any links containing spaces, so I'll just take that code out.

I mentioned the unneeded forward-slash escaping to Adam; hopefully he can correct that.

I'll also add code to preprocess the link tags and fixup the tooltips.

}
NativeApp.openURLInDefaultBrowser(url);
}
});

};

InlineDocsViewer.prototype.onAdded = function () {
InlineDocsViewer.prototype.parentClass.onAdded.apply(this, arguments);
window.setTimeout(this._sizeEditorToContent.bind(this));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for this to be async? I noticed the jump in size when initially displaying the docs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's what InlineColorEditor and the sample InlineImageViewer do, so I'm assuming there's some reason for it. But I agree it shouldn't be necessary -- we're already in the CM DOM by the time this gets called. I'll investigate a little more...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the setTimeout() was a hack specific to InlineImageViewer that got erroneously copied into InlineColorEditor. Inline text editors set their size synch in onAdded() and work just fine. So I'll remove the timeout from both here and InlineColorEditor.

};

InlineDocsViewer.prototype._sizeEditorToContent = function () {
this.hostEditor.setInlineWidgetHeight(this, this.$htmlContent.height() + 20, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping review here until final UI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually...what's the plan for updating the scrollers when the window resizes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By 'scrollers' I assume you mean the height of the inline widget? I hadn't thought of that :-) Will fix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Yes, thanks for assuming that.


// TODO: scroll only right-hand pane
// var summaryHt = this.$wrapperDiv.find(".css-prop-summary").height();
// this.hostEditor.setInlineWidgetHeight(this, summaryHt + 20, true);
};

module.exports = InlineDocsViewer;
});
Loading