From 2d65a3ccb1738b66db6f05d74c7b2dc34fb97335 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 9 Apr 2018 15:46:17 +0530 Subject: [PATCH 001/149] Fix to avoid window location change confirmation dialog while running tests - onbeforeunload conditional registration (#14169) * onunload conditional registration * Remove unused code --- src/document/DocumentCommandHandlers.js | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index a895ec16fb2..f1af9eef625 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -1722,28 +1722,29 @@ define(function (require, exports, module) { /** * Attach a beforeunload handler to notify user about unsaved changes and URL redirection in CEF. * Prevents data loss in scenario reported under #13708 + * Make sure we don't attach this handler if the current window is actually a test window **/ - window.onbeforeunload = function(e) { - if (window.location.pathname.indexOf('SpecRunner') > -1) { - return; - } - var openDocs = DocumentManager.getAllOpenDocuments(); + var isTestWindow = (new window.URLSearchParams(window.location.search || "")).get("testEnvironment"); + if (!isTestWindow) { + window.onbeforeunload = function(e) { + var openDocs = DocumentManager.getAllOpenDocuments(); - // Detect any unsaved changes - openDocs = openDocs.filter(function(doc) { - return doc && doc.isDirty; - }); + // Detect any unsaved changes + openDocs = openDocs.filter(function(doc) { + return doc && doc.isDirty; + }); - // Ensure we are not in normal app-quit or reload workflow - if (!_isReloading && !_windowGoingAway) { - if (openDocs.length > 0) { - return Strings.WINDOW_UNLOAD_WARNING_WITH_UNSAVED_CHANGES; - } else { - return Strings.WINDOW_UNLOAD_WARNING; + // Ensure we are not in normal app-quit or reload workflow + if (!_isReloading && !_windowGoingAway) { + if (openDocs.length > 0) { + return Strings.WINDOW_UNLOAD_WARNING_WITH_UNSAVED_CHANGES; + } else { + return Strings.WINDOW_UNLOAD_WARNING; + } } - } - }; + }; + } /** Do some initialization when the DOM is ready **/ AppInit.htmlReady(function () { From d52f3c9227e6955fc105c8c0b607f8acbc7479f0 Mon Sep 17 00:00:00 2001 From: Bhavika Miglani Date: Tue, 10 Apr 2018 11:29:15 +0530 Subject: [PATCH 002/149] Adding description string for Auto-update feature-flag/preference (#14208) * Adding strings for AutoUpdate and Related Files features * Addressing spacing issues * Normalizing whitespaces with spaces:4 Normalizing whitespaces with spaces:4 * Adding remaining strings for AutoUpdate feature * Removing unused string * Adding description string for Auto Update feature-flag/preference --- src/nls/root/strings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7a763f1946d..eb0d93b140d 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -864,6 +864,7 @@ define({ "DOWNLOAD_ERROR" : "Error occurred while downloading.", "RESTART_BUTTON" : "Restart", "LATER_BUTTON" : "Later", + "DESCRIPTION_AUTO_UPDATE" : "Enable/disable Brackets Auto-update", // Strings for Related Files "CMD_FIND_RELATED_FILES" : "Find Related Files" From a1515bf03f9609cffc2d75587b1fe6862a4826f0 Mon Sep 17 00:00:00 2001 From: Boopesh Mahendran Date: Mon, 16 Apr 2018 19:59:30 +0530 Subject: [PATCH 003/149] Add error strings --- src/nls/root/strings.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index eb0d93b140d..44faa0d4b54 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -66,6 +66,8 @@ define({ "ERROR_RENAMING_FILE_TITLE" : "Error Renaming {0}", "ERROR_RENAMING_FILE" : "An error occurred when trying to rename the {2} {0}. {1}", "ERROR_RENAMING_NOT_IN_PROJECT" : "The file or directory is not part of the currently opened project. Unfortunately, only project files can be renamed at this point.", + "ERROR_MOVING_FILE_TITLE" : "Error Moving {0}", + "ERROR_MOVING_FILE" : "An error occurred when trying to move the {2} {0}. {1}", "ERROR_DELETING_FILE_TITLE" : "Error Deleting {0}", "ERROR_DELETING_FILE" : "An error occurred when trying to delete the {2} {0}. {1}", "INVALID_FILENAME_TITLE" : "Invalid {0}", From 92b872e069ef9300c3cad7bc36189759db5fef2b Mon Sep 17 00:00:00 2001 From: Bhavika Miglani Date: Wed, 18 Apr 2018 13:13:47 +0530 Subject: [PATCH 004/149] AutoUpdate Framework (#14177) * AutoUpdate Framework : Initial Changes * Addressing unit testing issues, error handling, code cleanup * Clean up of code and spaces * Error handling and code cleanup changes * Addressing Review comments * Code cleanup * Code cleanup, for event dispatching and handling * Addressing review comments, adding feature-flag/preference for AutoUpdate, making AutoUpdate no-op for Linux, Replacing icons * Addressing review comments, minor code cleanup * Handling the node domain failure scenarios * Adding update_download_url to config.json and brackets.config.dist.json * Fixing eslint and jslint errors, cleaning up package.json, adding console log message on node domain failure --- src/brackets.config.dist.json | 3 +- src/config.json | 1 + src/document/DocumentCommandHandlers.js | 21 + .../default/AutoUpdate/MessageIds.js | 39 + .../default/AutoUpdate/StateHandler.js | 214 +++++ .../default/AutoUpdate/UpdateInfoBar.js | 174 ++++ .../default/AutoUpdate/UpdateStatus.js | 91 ++ .../AutoUpdate/htmlContent/updateBar.html | 20 + .../AutoUpdate/htmlContent/updateStatus.html | 5 + .../default/AutoUpdate/images/alert.svg | 10 + .../AutoUpdate/images/checkmarkcircle.svg | 10 + .../default/AutoUpdate/images/info.svg | 10 + src/extensions/default/AutoUpdate/main.js | 866 ++++++++++++++++++ .../AutoUpdate/node/AutoUpdateDomain.js | 379 ++++++++ .../default/AutoUpdate/node/package.json | 8 + .../default/AutoUpdate/styles/styles.css | 231 +++++ src/htmlContent/update-dialog.html | 4 +- src/htmlContent/update-list.html | 4 +- src/styles/brackets_patterns_override.less | 27 +- src/utils/UpdateNotification.js | 40 +- 20 files changed, 2143 insertions(+), 14 deletions(-) create mode 100644 src/extensions/default/AutoUpdate/MessageIds.js create mode 100644 src/extensions/default/AutoUpdate/StateHandler.js create mode 100644 src/extensions/default/AutoUpdate/UpdateInfoBar.js create mode 100644 src/extensions/default/AutoUpdate/UpdateStatus.js create mode 100644 src/extensions/default/AutoUpdate/htmlContent/updateBar.html create mode 100644 src/extensions/default/AutoUpdate/htmlContent/updateStatus.html create mode 100644 src/extensions/default/AutoUpdate/images/alert.svg create mode 100644 src/extensions/default/AutoUpdate/images/checkmarkcircle.svg create mode 100644 src/extensions/default/AutoUpdate/images/info.svg create mode 100644 src/extensions/default/AutoUpdate/main.js create mode 100644 src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js create mode 100644 src/extensions/default/AutoUpdate/node/package.json create mode 100644 src/extensions/default/AutoUpdate/styles/styles.css diff --git a/src/brackets.config.dist.json b/src/brackets.config.dist.json index e84b7671072..853735e3f72 100644 --- a/src/brackets.config.dist.json +++ b/src/brackets.config.dist.json @@ -2,5 +2,6 @@ "healthDataServerURL" : "https://health.brackets.io/healthDataLog", "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", "serviceKey" : "brackets-service", - "environment" : "production" + "environment" : "production", + "update_download_url" : "https://github.com/adobe/brackets/releases/download/" } diff --git a/src/config.json b/src/config.json index e5eecc04596..b55f273b652 100644 --- a/src/config.json +++ b/src/config.json @@ -20,6 +20,7 @@ "extension_url": "https://s3.amazonaws.com/extend.brackets/{0}/{0}-{1}.zip", "linting.enabled_by_default": true, "build_timestamp": "", + "update_download_url": "https://github.com/adobe/brackets/releases/download/", "healthDataServerURL": "https://healthdev.brackets.io/healthDataLog", "analyticsDataServerURL": "https://cc-api-data-stage.adobe.io/ingest", "serviceKey": "brackets-service", diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index f1af9eef625..a37e22bb96f 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -31,6 +31,7 @@ define(function (require, exports, module) { CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), DeprecationWarning = require("utils/DeprecationWarning"), + EventDispatcher = require("utils/EventDispatcher"), ProjectManager = require("project/ProjectManager"), DocumentManager = require("document/DocumentManager"), MainViewManager = require("view/MainViewManager"), @@ -135,7 +136,14 @@ define(function (require, exports, module) { PreferencesManager.definePreference("defaultExtension", "string", "", { excludeFromHints: true }); + EventDispatcher.makeEventDispatcher(exports); + /** + * Event triggered when File Save is cancelled, when prompted to save dirty files + */ + var APP_QUIT_CANCELLED = "appQuitCancelled"; + + /** * JSLint workaround for circular dependency * @type {function} @@ -847,6 +855,14 @@ define(function (require, exports, module) { return result.promise(); } + + /** + * Dispatches the app quit cancelled event + */ + function dispatchAppQuitCancelledEvent() { + exports.trigger(exports.APP_QUIT_CANCELLED); + } + /** * Opens the native OS save as dialog and saves document. @@ -996,6 +1012,7 @@ define(function (require, exports, module) { if (selectedPath) { _doSaveAfterSaveDialog(selectedPath); } else { + dispatchAppQuitCancelledEvent(); result.reject(USER_CANCELED); } } else { @@ -1217,6 +1234,7 @@ define(function (require, exports, module) { ) .done(function (id) { if (id === Dialogs.DIALOG_BTN_CANCEL) { + dispatchAppQuitCancelledEvent(); result.reject(); } else if (id === Dialogs.DIALOG_BTN_OK) { // "Save" case: wait until we confirm save has succeeded before closing @@ -1322,6 +1340,7 @@ define(function (require, exports, module) { ) .done(function (id) { if (id === Dialogs.DIALOG_BTN_CANCEL) { + dispatchAppQuitCancelledEvent(); result.reject(); } else if (id === Dialogs.DIALOG_BTN_OK) { // Save all unsaved files, then if that succeeds, close all @@ -1784,6 +1803,8 @@ define(function (require, exports, module) { // Define public API exports.showFileOpenError = showFileOpenError; + exports.APP_QUIT_CANCELLED = APP_QUIT_CANCELLED; + // Deprecated commands CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.FILE_ADD_TO_WORKING_SET, handleFileAddToWorkingSet); diff --git a/src/extensions/default/AutoUpdate/MessageIds.js b/src/extensions/default/AutoUpdate/MessageIds.js new file mode 100644 index 00000000000..22ee753bfef --- /dev/null +++ b/src/extensions/default/AutoUpdate/MessageIds.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + exports.DOWNLOAD_INSTALLER = "node.downloadInstaller"; + exports.PERFORM_CLEANUP = "node.performCleanup"; + exports.VALIDATE_INSTALLER = "node.validateInstaller"; + exports.INITIALIZE_STATE = "node.initializeState"; + exports.SHOW_STATUS_INFO = "brackets.showStatusInfo"; + exports.NOTIFY_DOWNLOAD_SUCCESS = "brackets.notifyDownloadSuccess"; + exports.SHOW_ERROR_MESSAGE = "brackets.showErrorMessage"; + exports.NOTIFY_DOWNLOAD_FAILURE = "brackets.notifyDownloadFailure"; + exports.NOTIFY_SAFE_TO_DOWNLOAD = "brackets.notifySafeToDownload"; + exports.NOTIFY_INITIALIZATION_COMPLETE = "brackets.notifyinitializationComplete"; + exports.NOTIFY_VALIDATION_STATUS = "brackets.notifyvalidationStatus"; + exports.REGISTER_BRACKETS_FUNCTIONS = "brackets.registerBracketsFunctions"; +}); diff --git a/src/extensions/default/AutoUpdate/StateHandler.js b/src/extensions/default/AutoUpdate/StateHandler.js new file mode 100644 index 00000000000..533368890e1 --- /dev/null +++ b/src/extensions/default/AutoUpdate/StateHandler.js @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var FileSystem = brackets.getModule("filesystem/FileSystem"), + FileUtils = brackets.getModule("file/FileUtils"); + + var FILE_NOT_FOUND = 0, + FILE_NOT_READ = 1, + FILE_PARSE_EXCEPTION = 2, + FILE_READ_FAIL = 3; + + /** + * @constructor + * Creates a StateHandler object for a JSON file. It maintains the following : + * path - path to the JSON file, + * state - parsed content of the file + * @param {string} path - path to the JSON file + */ + var StateHandler = function (path) { + this.path = path; + this.state = null; + }; + + /** + * Checks if the file exists + * @returns {$.Deferred} - a jquery deferred promise, + * that is resolved with existence or non-existence + * of json file. + */ + StateHandler.prototype.exists = function () { + var result = $.Deferred(), + _file = FileSystem.getFileForPath(this.path); + + _file.exists(function (err, exists) { + if (err) { + result.reject(); + } else if (exists) { + result.resolve(); + } else { + result.reject(); + } + }); + + return result.promise(); + }; + + /** + * Parses the JSON file, and maintains a state for the parsed data + * @returns {$.Deferred} - a jquery deferred promise, + * that is resolved with a parsing success or failure + */ + StateHandler.prototype.parse = function () { + var result = $.Deferred(), + _file = FileSystem.getFileForPath(this.path); + var self = this; + + this.exists() + .done(function () { + FileUtils.readAsText(_file) + .done(function (text) { + try { + if (text) { + self.state = JSON.parse(text); + result.resolve(); + } else { + result.reject(FILE_READ_FAIL); + } + } catch (error) { + result.reject(FILE_PARSE_EXCEPTION); + } + }) + .fail(function () { + result.reject(FILE_NOT_READ); + }); + + }) + .fail(function () { + result.reject(FILE_NOT_FOUND); + }); + + return result.promise(); + }; + + /** + * Sets the value of a key in a json file. + * @param {string} key - key for which the value is to be set + * @param {string} value - the value to be set for the given key + * @returns {$.Deferred} - a jquery deferred promise, that is resolved with a write success or failure + */ + StateHandler.prototype.set = function (key, value) { + this.state = this.state || {}; + this.state[key] = value; + + return this.write(true); + }; + + /** + * Gets the value for a given key, from the in-memory state maintained for a json file. + * @param {string} key - key for which value is to be retrieved + * @returns {string} value for the given key + */ + StateHandler.prototype.get = function (key) { + var retval = null; + + if (this.state && this.state[key]) { + retval = this.state[key]; + } + + return retval; + }; + + + /** + * Performs the write of JSON object to a file. + * @param {string} filepath - path to JSON file + * @param {object} json - JSON object to write + * @returns {$.Deferred} - a jquery deferred promise, + * that is resolved with the write success or failure + */ + function _write(filePath, json) { + var result = $.Deferred(), + _file = FileSystem.getFileForPath(filePath); + + if (_file) { + + var content = JSON.stringify(json); + FileUtils.writeText(_file, content, true) + .done(function () { + result.resolve(); + }) + .fail(function (err) { + result.reject(); + }); + + } else { + result.reject(); + } + + return result.promise(); + } + /** + * Writes content into a json file + * @param {boolean} overwrite - true if file is to be overwritten, false otherwise + * @param {object} [content=this.state] - content to be written into the json file. + * @returns {$.Deferred} - a jquery deferred promise, that is resolved with a write success or failure + */ + StateHandler.prototype.write = function (overwrite) { + var result = $.Deferred(), + self = this; + + function writePromise(path, contentToWrite) { + _write(path, contentToWrite) + .done(function () { + result.resolve(); + }) + .fail(function (err) { + result.reject(); + }); + } + + var content = self.state; + if (overwrite) { + writePromise(self.path, content); + } else { + //check for existence + self.exists() + .fail(function () { + writePromise(self.path, content); + }).done(function () { + result.reject(); + }); + } + + return result.promise(); + }; + + /** + * Resets the content of the in-memory state + */ + StateHandler.prototype.reset = function () { + this.state = null; + }; + + exports.StateHandler = StateHandler; + exports.MessageKeys = { + FILE_NOT_FOUND: FILE_NOT_FOUND, + FILE_NOT_READ: FILE_NOT_READ, + FILE_PARSE_EXCEPTION: FILE_PARSE_EXCEPTION, + FILE_READ_FAIL: FILE_READ_FAIL + }; +}); diff --git a/src/extensions/default/AutoUpdate/UpdateInfoBar.js b/src/extensions/default/AutoUpdate/UpdateInfoBar.js new file mode 100644 index 00000000000..b7b689e77dd --- /dev/null +++ b/src/extensions/default/AutoUpdate/UpdateInfoBar.js @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var MainViewManager = brackets.getModule("view/MainViewManager"), + Mustache = brackets.getModule("thirdparty/mustache/mustache"), + EventDispatcher = brackets.getModule("utils/EventDispatcher"), + UpdateBarHtml = require("text!htmlContent/updateBar.html"), + Strings = brackets.getModule("strings"); + + EventDispatcher.makeEventDispatcher(exports); + + /** Event triggered when Restart button is clicked on the update bar + */ + var RESTART_BTN_CLICKED = "restartBtnClicked"; + + /** Event triggered when Later button is clicked on the update bar + */ + var LATER_BTN_CLICKED = "laterBtnClicked"; + + // Key handlers for buttons in UI + var SPACE_KEY = 32, // keycode for space key + ESC_KEY = 27; // keycode for escape key + + /** + * Generates the json to be used by Mustache for rendering + * @param {object} msgObj - json object containing message information to be displayed + * @returns {object} - the generated json object + */ + function generateJsonForMustache(msgObj) { + var msgJsonObj = {}; + if (msgObj.type) { + msgJsonObj.type = "'" + msgObj.type + "'"; + } + msgJsonObj.title = msgObj.title; + msgJsonObj.description = msgObj.description; + if (msgObj.needButtons) { + msgJsonObj.buttons = [{ + "id": "restart", + "value": Strings.RESTART_BUTTON, + "tIndex": "'0'" + }, { + "id": "later", + "value": Strings.LATER_BUTTON, + "tIndex": "'0'" + }]; + msgJsonObj.needButtons = msgObj.needButtons; + } + return msgJsonObj; + } + + /** + * Removes and cleans up the update bar from DOM + */ + function cleanUpdateBar() { + var $updateBar = $('#update-bar'); + if ($updateBar.length > 0) { + $updateBar.remove(); + } + $(window.document).off("keydown.AutoUpdate"); + } + + /** + * Displays the Update Bar UI + * @param {object} msgObj - json object containing message info to be displayed + * + */ + function showUpdateBar(msgObj) { + var jsonToMustache = generateJsonForMustache(msgObj), + $updateBarElement = $(Mustache.render(UpdateBarHtml, jsonToMustache)); + + cleanUpdateBar(); //Remove an already existing update bar, if any + $updateBarElement.prependTo(".content"); + + var $updateBar = $('#update-bar'), + $updateContent = $updateBar.find('#update-content'), + $contentContainer = $updateBar.find('#content-container'), + $heading = $updateBar.find('#heading'), + $description = $updateBar.find('#description'), + $restart = $updateBar.find('#update-btn-restart'), + $later = $updateBar.find('#update-btn-later'), + $closeIcon = $updateBar.find('#close-icon'); + + if ($updateContent.length > 0) { + if ($updateContent[0].scrollWidth > $updateContent.innerWidth()) { + //Text has over-flown, show the update content as tooltip message + if ($contentContainer.length > 0 && + $heading.length > 0 && + $description.length > 0) { + $contentContainer.attr("title", $heading.text() + $description.text()); + } + } + } + + //Event handlers on the Update Bar + + // Click and key handlers on Restart button + if ($restart.length > 0) { + $restart.click(function () { + cleanUpdateBar(); + exports.trigger(exports.RESTART_BTN_CLICKED); + }); + + $restart.keyup(function (event) { + if (event.which === SPACE_KEY) { + $restart.trigger('click'); + } + }); + } + + // Click and key handlers on Later button + if ($later.length > 0) { + $later.click(function () { + cleanUpdateBar(); + MainViewManager.focusActivePane(); + exports.trigger(exports.LATER_BTN_CLICKED); + }); + + $later.keyup(function (event) { + if (event.which === SPACE_KEY) { + $later.trigger('click'); + } + }); + } + + // Click and key handlers on Close button + if ($closeIcon.length > 0) { + $closeIcon.click(function () { + cleanUpdateBar(); + MainViewManager.focusActivePane(); + }); + + $closeIcon.keyup(function (event) { + if (event.which === SPACE_KEY) { + $closeIcon.trigger('click'); + } + }); + } + $(window.document).on("keydown.AutoUpdate", function (event) { + var code = event.which; + if (code === ESC_KEY) { + // Keyboard input of Esc key on Update Bar dismisses and removes the bar + cleanUpdateBar(); + MainViewManager.focusActivePane(); + event.stopImmediatePropagation(); + } + }); + } + exports.showUpdateBar = showUpdateBar; + exports.RESTART_BTN_CLICKED = RESTART_BTN_CLICKED; + exports.LATER_BTN_CLICKED = LATER_BTN_CLICKED; +}); diff --git a/src/extensions/default/AutoUpdate/UpdateStatus.js b/src/extensions/default/AutoUpdate/UpdateStatus.js new file mode 100644 index 00000000000..0d459350974 --- /dev/null +++ b/src/extensions/default/AutoUpdate/UpdateStatus.js @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var UpdateStatusHtml = require("text!htmlContent/updateStatus.html"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + Mustache = brackets.getModule("thirdparty/mustache/mustache"), + Strings = brackets.getModule("strings"); + + ExtensionUtils.loadStyleSheet(module, "styles/styles.css"); + + /** + * Cleans up status information from Status Bar + */ + function cleanUpdateStatus() { + $('#update-status').remove(); + } + + /** + * Displays the status information on Status Bar + * @param {string} id - the id of string to display + */ + function showUpdateStatus(id) { + cleanUpdateStatus(); + + var $updateStatus = $(Mustache.render(UpdateStatusHtml, {"Strings": Strings})); + $updateStatus.appendTo('#status-bar'); + $('#update-status #' + id).show(); + } + + /** + * Modifies the status information on Status Bar + * @param {object} statusObj - json containing status info - { + * target - id of string to modify, + * spans - array of objects of type - { + * id - id of span for modifiable substring, + * val - the new value to modifiable substring } + * } + */ + function modifyUpdateStatus(statusObj) { + statusObj.spans.forEach(function (span) { + $('#update-status #' + statusObj.target + ' #' + span.id).text(span.val); + }); + } + + /** + * Displays the progress bar on Status bar, while the download is in progress + * @param {object} statusObj - json containing status info - { + * target - id of string to modify, + * spans - array of objects of type - { + * id - id of span for modifiable substring, + * val - the new value to modifiable substring } + * } + */ + function displayProgress(statusObj) { + statusObj.spans.forEach(function (span) { + if (span.id === 'percent') { + var bgval = 'linear-gradient(to right, #1474BF ' + span.val + ', rgba(0, 0, 0, 0) 0%'; + $('#update-status').css('background', bgval); + } + }); + + } + + exports.showUpdateStatus = showUpdateStatus; + exports.modifyUpdateStatus = modifyUpdateStatus; + exports.cleanUpdateStatus = cleanUpdateStatus; + exports.displayProgress = displayProgress; +}); diff --git a/src/extensions/default/AutoUpdate/htmlContent/updateBar.html b/src/extensions/default/AutoUpdate/htmlContent/updateBar.html new file mode 100644 index 00000000000..9014aac8d21 --- /dev/null +++ b/src/extensions/default/AutoUpdate/htmlContent/updateBar.html @@ -0,0 +1,20 @@ +
+
+ +
+
+

{{title}}  {{{description}}}

+
+ {{#needButtons}} +
+ {{#buttons}} + + {{/buttons}} +
+ {{/needButtons}} + {{^buttons}} +
+ +
+ {{/buttons}} +
\ No newline at end of file diff --git a/src/extensions/default/AutoUpdate/htmlContent/updateStatus.html b/src/extensions/default/AutoUpdate/htmlContent/updateStatus.html new file mode 100644 index 00000000000..a7de44f3c3f --- /dev/null +++ b/src/extensions/default/AutoUpdate/htmlContent/updateStatus.html @@ -0,0 +1,5 @@ +
+

{{Strings.INITIAL_DOWNLOAD}}0%

+

{{Strings.RETRY_DOWNLOAD}}1/5

+

{{Strings.VALIDATING_INSTALLER}}

+
diff --git a/src/extensions/default/AutoUpdate/images/alert.svg b/src/extensions/default/AutoUpdate/images/alert.svg new file mode 100644 index 00000000000..2390583d92f --- /dev/null +++ b/src/extensions/default/AutoUpdate/images/alert.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/extensions/default/AutoUpdate/images/checkmarkcircle.svg b/src/extensions/default/AutoUpdate/images/checkmarkcircle.svg new file mode 100644 index 00000000000..fc0706d5d99 --- /dev/null +++ b/src/extensions/default/AutoUpdate/images/checkmarkcircle.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/extensions/default/AutoUpdate/images/info.svg b/src/extensions/default/AutoUpdate/images/info.svg new file mode 100644 index 00000000000..5b23b6491fd --- /dev/null +++ b/src/extensions/default/AutoUpdate/images/info.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js new file mode 100644 index 00000000000..5d062c0eec0 --- /dev/null +++ b/src/extensions/default/AutoUpdate/main.js @@ -0,0 +1,866 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ +/* eslint-disable indent */ +/* eslint-disable max-len */ + +define(function (require, exports, module) { + "use strict"; + + var CommandManager = brackets.getModule("command/CommandManager"), + Commands = brackets.getModule("command/Commands"), + AppInit = brackets.getModule("utils/AppInit"), + UpdateNotification = brackets.getModule("utils/UpdateNotification"), + DocumentCommandHandlers = brackets.getModule("document/DocumentCommandHandlers"), + NodeDomain = brackets.getModule("utils/NodeDomain"), + FileSystem = brackets.getModule("filesystem/FileSystem"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + FileUtils = brackets.getModule("file/FileUtils"), + Strings = brackets.getModule("strings"), + StateHandlerModule = require("StateHandler"), + MessageIds = require("MessageIds"), + UpdateStatus = require("UpdateStatus"), + UpdateInfoBar = require("UpdateInfoBar"); + + + var _modulePath = FileUtils.getNativeModuleDirectoryPath(module), + _nodePath = "node/AutoUpdateDomain", + _domainPath = [_modulePath, _nodePath].join("/"), + updateDomain; + + var appSupportDirectory = brackets.app.getApplicationSupportDirectory(), + updateDir = appSupportDirectory + '/updateTemp', + updateJsonPath = updateDir + '/' + 'updateHelper.json'; + + var StateHandler = StateHandlerModule.StateHandler, + StateHandlerMessages = StateHandlerModule.MessageKeys; + + var updateJsonHandler; + + var MAX_DOWNLOAD_ATTEMPTS = 6, + downloadAttemptsRemaining; + + // function map for brackets functions + var functionMap = {}; + + var _updateParams; + + //Namespacing the event + var APP_QUIT_CANCELLED = DocumentCommandHandlers.APP_QUIT_CANCELLED + ".auto-update"; + + var _nodeErrorMessages = { + UPDATEDIR_READ_FAILED: 0, + UPDATEDIR_CLEAN_FAILED: 1, + CHECKSUM_DID_NOT_MATCH: 2, + INSTALLER_NOT_FOUND: 3, + DOWNLOAD_ERROR: 4 + }; + + + /** + * Checks if auto update preference is enabled or disabled + * @private + * @returns {boolean} - true if preference enabled, false otherwise + */ + function _isAutoUpdateEnabled() { + return (PreferencesManager.get("autoUpdate.AutoUpdate") !== false); + } + + /** + * Receives messages from node + * @param {object} event - event received from node + * @param {object} msgObj - json containing - { + * fn - function to execute on Brackets side + * args - arguments to the above function + */ + function receiveMessageFromNode(event, msgObj) { + functionMap[msgObj.fn].apply(null, msgObj.args); + } + + /* + * Checks if Brackets version got updated + * @returns {boolean} true if version updated, false otherwise + */ + function checkIfVersionUpdated() { + + var latestBuildNumber = updateJsonHandler.get("latestBuildNumber"), + currentBuildNumber = Number(/-([0-9]+)/.exec(brackets.metadata.version)[1]); + + return latestBuildNumber === currentBuildNumber; + } + + + /** + * Gets the arguments to a function in an array + * @param {object} args - the arguments object + * @returns {Array} - array of actual arguments + */ + function getFunctionArgs(args) { + if (args.length > 1) { + var fnArgs = new Array(args.length - 1), + i; + + for (i = 1; i < args.length; ++i) { + fnArgs[i - 1] = args[i]; + } + return fnArgs; + } + return []; + } + + + /** + * Posts messages to node + * @param {string} messageId - Message to be passed + */ + function postMessageToNode(messageId) { + var msg = { + fn: messageId, + args: getFunctionArgs(arguments) + }; + updateDomain.exec('data', msg); + } + + + /** + * Checks and handles the update success and failure scenarios + */ + function checkUpdateStatus() { + var filesToCache = null, + downloadCompleted = updateJsonHandler.get("downloadCompleted"), + updateInitiatedInPrevSession = updateJsonHandler.get("updateInitiatedInPrevSession"); + + if (downloadCompleted && updateInitiatedInPrevSession) { + var isNewVersion = checkIfVersionUpdated(); + if (isNewVersion) { + // We get here if the update was successful + UpdateInfoBar.showUpdateBar({ + type: "success", + title: Strings.UPDATE_SUCCESSFUL, + description: "" + }); + } else { + // We get here if the update started but failed + filesToCache = ['.logs']; //AUTOUPDATE_PRERELEASE + UpdateInfoBar.showUpdateBar({ + type: "error", + title: Strings.UPDATE_FAILED, + description: Strings.GO_TO_SITE + }); + } + } else if (downloadCompleted && !updateInitiatedInPrevSession) { + // We get here if the download was complete and user selected UpdateLater + if (brackets.platform === "mac") { + filesToCache = ['.dmg', '.json']; + } else if (brackets.platform === "win") { + filesToCache = ['.msi', '.json']; + } + } + + postMessageToNode(MessageIds.PERFORM_CLEANUP, filesToCache); + } + + + /** + * Initializes the state of parsed content from updateHelper.json + */ + function initState() { + updateJsonHandler.parse() + .done(function () { + checkUpdateStatus(); + }) + .fail(function (code) { + var logMsg; + switch (code) { + case StateHandlerMessages.FILE_NOT_FOUND: + logMsg = "AutoUpdate : updateHelper.json cannot be parsed, does not exist"; + break; + case StateHandlerMessages.FILE_NOT_READ: + logMsg = "AutoUpdate : updateHelper.json could not be read"; + break; + case StateHandlerMessages.FILE_PARSE_EXCEPTION: + logMsg = "AutoUpdate : updateHelper.json could not be parsed, exception encountered"; + break; + case StateHandlerMessages.FILE_READ_FAIL: + logMsg = "AutoUpdate : updateHelper.json could not be parsed"; + break; + } + console.log(logMsg); + }); + } + + + + /** + * Sets up the Auto Update environment + */ + function setupAutoUpdate() { + updateJsonHandler = new StateHandler(updateJsonPath); + + updateDomain.exec('initNode', { + messageIds: MessageIds, + updateDir: updateDir + }); + + updateDomain.on('data', receiveMessageFromNode); + initState(); + } + + + /** + * Generates the extension for installer file, based on platform + * @returns {object} - json containing platform Info : { + * extension - installer file extension, + * OS - current OS } + */ + function getPlatformInfo() { + var ext = "", + OS = ""; + + if (/Windows|Win32|WOW64|Win64/.test(window.navigator.userAgent)) { + OS = "WIN"; + ext = ".msi"; + } else if (/Mac/.test(window.navigator.userAgent)) { + OS = "OSX"; + ext = ".dmg"; + } else if (/Linux|X11/.test(window.navigator.userAgent)) { + OS = "LINUX32"; + ext = ".32-bit.deb"; + if (/x86_64/.test(window.navigator.appVersion + window.navigator.userAgent)) { + OS = "LINUX64"; + ext = ".64-bit.deb"; + } + } + + return { + extension: ext, + OS: OS + }; + } + + + + /** + * Generates the download URL for the update installer, based on platform + * @param {string} buildName - name of latest build + * @param {string} ext - file extension, based on platform + * @returns {object} - downloadInfo json, containing installer name and download URL + */ + function getDownloadInfo(buildName, ext) { + var downloadInfo = {}; + if (buildName) { + var buildNum = buildName.match(/([\d.]+)/); + if (buildNum) { + buildNum = buildNum[1]; + + var tag = buildName.toLowerCase().split(" ").join("-"), + installerName = "Brackets." + buildName.split(" ").join(".") + ext, + downloadURL; + + downloadURL = brackets.config.update_download_url + tag + "/" + installerName; + + downloadInfo = { + installerName: installerName, + downloadURL: downloadURL + }; + + } + } + + return downloadInfo; + } + + + /** + * Initializes the state for AutoUpdate process + * @returns {$.Deferred} - a jquery promise, + * that is resolved with success or failure + * of state initialization + */ + function initializeState() { + var result = $.Deferred(); + + FileSystem.resolve(updateDir, function (err) { + if (!err) { + result.resolve(); + } else { + var directory = FileSystem.getDirectoryForPath(updateDir); + directory.create(function (error) { + if (error) { + console.error('AutoUpdate : Error in creating update directory in Appdata'); + result.reject(); + } else { + result.resolve(); + } + }); + } + }); + + return result.promise(); + } + + + + /** + * Handles the auto update event, which is triggered when user clicks GetItNow in UpdateNotification dialog + * @param {object} updateParams - json object containing update information { + * installerName - name of the installer + * downloadURL - download URL + * latestBuildNumber - build number + * checksum - checksum } + */ + function initiateAutoUpdate(updateParams) { + _updateParams = updateParams; + downloadAttemptsRemaining = MAX_DOWNLOAD_ATTEMPTS; + + initializeState() + .done(function () { + postMessageToNode(MessageIds.INITIALIZE_STATE, _updateParams); + }) + .fail(function () { + UpdateInfoBar.showUpdateBar({ + type: "error", + title: Strings.INITIALISATION_FAILED, + description: "" + }); + }); + } + + + /** + * Handles and processes the update info, required for app auto update + * @private + * @param {Array} updates - array object containing info about updates + */ + function _updateProcessHandler(updates) { + //If no checksum field is present then we're setting it to 0, just as a safety check, + // although ideally this situation should never occur in releases post its introduction. + var platformInfo = getPlatformInfo(), + buildName = updates[0].versionString, + checksum = (updates[0].checksums) ? updates[0].checksums[platformInfo.OS] : 0; + + var updateParams = getDownloadInfo(buildName, platformInfo.extension); + updateParams.latestBuildNumber = updates[0].buildNumber; + updateParams.checksum = checksum; + + //Initiate the auto update, with update params + initiateAutoUpdate(updateParams); + } + + + /** + * Unregisters the App Quit event handler + */ + function resetAppQuitHandler() { + DocumentCommandHandlers.off(APP_QUIT_CANCELLED); + } + + + /** + * Unsets the Auto Update environment + */ + function unsetAutoUpdate() { + updateJsonHandler = null; + updateDomain.off('data'); + resetAppQuitHandler(); + } + + + /** + * Defines preference to enable/disable Auto Update + */ + function setupAutoUpdatePreference() { + PreferencesManager.definePreference("autoUpdate.AutoUpdate", "boolean", true, { + description: Strings.DESCRIPTION_AUTO_UPDATE + }); + + // Set or unset the auto update, based on preference state change + PreferencesManager.on("change", "autoUpdate.AutoUpdate", function () { + if (_isAutoUpdateEnabled()) { + setupAutoUpdate(); + UpdateNotification.registerUpdateHandler(_updateProcessHandler); + } else { + unsetAutoUpdate(); + UpdateNotification.resetToDefaultUpdateHandler(); + } + }); + } + + + /** + * Creates the Node Domain for Auto Update + */ + function setupAutoUpdateDomain() { + updateDomain = new NodeDomain("AutoUpdate", _domainPath); + } + + + /** + * Overriding the appReady for Auto update + */ + + AppInit.appReady(function () { + + // Auto Update is supported on Win and Mac, as of now + if (brackets.platform === "linux" || !(brackets.app.setUpdateParams)) { + return; + } + setupAutoUpdateDomain(); + + //Bail out if update domain could not be created + if (!updateDomain) { + return; + } + + // Check if the update domain is properly initialised + updateDomain.promise() + .done(function () { + setupAutoUpdatePreference(); + if (_isAutoUpdateEnabled()) { + setupAutoUpdate(); + UpdateNotification.registerUpdateHandler(_updateProcessHandler); + } + }) + .fail(function (err) { + console.error("AutoUpdate : node domain could not be initialized."); + return; + }); + }); + + + /** + * Enables/disables the state of "Check For Updates" menu entry under Help Menu + */ + function enableCheckForUpdateEntry(enable) { + var cfuCommand = CommandManager.get(Commands.HELP_CHECK_FOR_UPDATE); + cfuCommand.setEnabled(enable); + } + + /** + * Checks if it is the first iteration of download + * @returns {boolean} - true if first iteration, false if it is a retrial of download + */ + function isFirstIterationDownload() { + return (downloadAttemptsRemaining === MAX_DOWNLOAD_ATTEMPTS); + } + + /** + * Resets the update state in updatehelper.json in case of failure, + * and logs an error with the message + * @param {string} message - the message to be logged onto console + */ + function resetStateInFailure(message) { + updateJsonHandler.reset(); + + UpdateInfoBar.showUpdateBar({ + type: "error", + title: Strings.UPDATE_FAILED, + description: "" + }); + + enableCheckForUpdateEntry(true); + console.error(message); + } + + /** + * Sets the update state in updateHelper.json in Appdata + * @param {string} key - key to be set + * @param {string} value - value to be set + * @param {function} fn - the function + * to be called in case of + * successful setting of update state + */ + function setUpdateStateInJSON(key, value, fn) { + var func = fn || function () {}; + updateJsonHandler.set(key, value) + .done(function () { + func(); + }) + .fail(function () { + resetStateInFailure("AutoUpdate : Could not modify updatehelper.json"); + }); + } + + /** + * Handles a safe download of the latest installer, + * safety is ensured by cleaning up any pre-existing installers + * from update directory before beginning a fresh download + */ + function handleSafeToDownload() { + var downloadFn = function () { + if (isFirstIterationDownload()) { + // For the first iteration of download, show download + //status info in Status bar, and pass download to node + UpdateStatus.showUpdateStatus("initial-download"); + postMessageToNode(MessageIds.DOWNLOAD_INSTALLER, true); + } else { + /* For the retry iterations of download, modify the + download status info in Status bar, and pass download to node */ + var attempt = (MAX_DOWNLOAD_ATTEMPTS - downloadAttemptsRemaining); + if (attempt > 1) { + var info = attempt.toString() + "/5"; + var status = { + target: "retry-download", + spans: [{ + id: "attempt", + val: info + }] + }; + UpdateStatus.modifyUpdateStatus(status); + } else { + UpdateStatus.showUpdateStatus("retry-download"); + } + postMessageToNode(MessageIds.DOWNLOAD_INSTALLER, false); + } + + --downloadAttemptsRemaining; + }; + setUpdateStateInJSON('downloadCompleted', false, downloadFn); + } + + /** + * Checks if there is an active internet connection available + * @returns {boolean} - true if online, false otherwise + */ + function checkIfOnline() { + return window.navigator.onLine; + } + + /** + * Attempts a download of the latest installer, while cleaning up any existing downloaded installers + */ + function attemptToDownload() { + if (checkIfOnline()) { + postMessageToNode(MessageIds.PERFORM_CLEANUP, ['.json'], true); + } else { + UpdateInfoBar.showUpdateBar({ + type: "warning", + title: Strings.DOWNLOAD_FAILED, + description: Strings.INTERNET_UNAVAILABLE + }); + } + } + + /** + * Validates the checksum of a file against a given checksum + * @param {object} params - json containing { + * filePath - path to the file, + * expectedChecksum - the checksum to validate against } + */ + function validateChecksum(params) { + postMessageToNode(MessageIds.VALIDATE_INSTALLER, params); + } + + /** + * Gets the latest installer, by either downloading a new one or fetching the cached download. + */ + function getLatestInstaller() { + var downloadCompleted = updateJsonHandler.get('downloadCompleted'); + if (!downloadCompleted) { + attemptToDownload(); + } else { + validateChecksum(); + } + } + + /** + * Handles the show status information callback from Node. + * It modifies the info displayed on Status bar. + * @param {object} statusObj - json containing status info { + * target - id of string to display, + * spans - Array containing json objects of type - { + * id - span id, + * val - string to fill the span element with } + * } + */ + function showStatusInfo(statusObj) { + if (statusObj.target === "initial-download") { + UpdateStatus.modifyUpdateStatus(statusObj); + } + UpdateStatus.displayProgress(statusObj); + } + + /** + * Handles the error messages from Node, in a popup displayed to the user. + * @param {string} message - error string + */ + function showErrorMessage(message) { + var descriptionMessage; + + switch (message) { + case _nodeErrorMessages.UPDATEDIR_READ_FAILED: + descriptionMessage = Strings.UPDATEDIR_READ_FAILED; + break; + case _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED: + descriptionMessage = Strings.UPDATEDIR_CLEAN_FAILED; + break; + } + + UpdateInfoBar.showUpdateBar({ + type: "error", + title: Strings.CLEANUP_FAILED, + description: descriptionMessage + }); + } + + + /** + * Handles the Cancel button click by user in + * Unsaved changes prompt, which would come up if user + * has dirty files and he/she clicked UpdateNow + */ + function dirtyFileSaveCancelled() { + UpdateInfoBar.showUpdateBar({ + type: "warning", + title: Strings.WARNING_TYPE, + description: Strings.UPDATE_ON_NEXT_LAUNCH + }); + } + + /** + * Registers the App Quit event handler, in case of dirty + * file save cancelled scenario, while Auto Update is scheduled to run on quit + */ + function setAppQuitHandler() { + resetAppQuitHandler(); + DocumentCommandHandlers.on(APP_QUIT_CANCELLED, dirtyFileSaveCancelled); + } + + + /** + * Initiates the update process, when user clicks UpdateNow in the update popup + * @param {string} formattedInstallerPath - formatted path to the latest installer + * @param {string} formattedLogFilePath - formatted path to the installer log file + * @param {string} installStatusFilePath - path to the install status log file + */ + function initiateUpdateProcess(formattedInstallerPath, formattedLogFilePath, installStatusFilePath) { + + // Get additional update parameters on Mac : installDir, appName, and updateDir + function getAdditionalParams() { + var retval = {}; + var installDir = FileUtils.getNativeBracketsDirectoryPath(); + + if (installDir) { + var appPath = installDir.split("/Contents/www")[0]; + installDir = appPath.substr(0, appPath.lastIndexOf('/')); + var appName = appPath.substr(appPath.lastIndexOf('/') + 1); + + retval = { + installDir: installDir, + appName: appName, + updateDir: updateDir + }; + } + return retval; + } + + // Update function, to carry out app update + var updateFn = function () { + var infoObj = { + installerPath: formattedInstallerPath, + logFilePath: formattedLogFilePath, + installStatusFilePath: installStatusFilePath + }; + + if (brackets.platform === "mac") { + var additionalParams = getAdditionalParams(), + key; + + for (key in additionalParams) { + if (additionalParams.hasOwnProperty(key)) { + infoObj[key] = additionalParams[key]; + } + } + } + + // Set update parameters for app update + if (brackets.app.setUpdateParams) { + brackets.app.setUpdateParams(JSON.stringify(infoObj), function (err) { + if (err) { + resetStateInFailure("AutoUpdate : Update parameters could not be set for the installer. Error encountered: " + err); + } else { + setAppQuitHandler(); + CommandManager.execute(Commands.FILE_QUIT); + } + }); + } else { + resetStateInFailure("AutoUpdate : setUpdateParams could not be found in shell"); + } + }; + setUpdateStateInJSON('updateInitiatedInPrevSession', true, updateFn); + } + + /** + * Detaches the Update Bar Buttons event handlers + */ + function detachUpdateBarBtnHandlers() { + UpdateInfoBar.off(UpdateInfoBar.RESTART_BTN_CLICKED); + UpdateInfoBar.off(UpdateInfoBar.LATER_BTN_CLICKED); + } + + + /** + * Handles the installer validation callback from Node + * @param {object} statusObj - json containing - { + * valid - (boolean)true for a valid installer, false otherwise, + * installerPath, logFilePath, + * installStatusFilePath - for a valid installer, + * err - for an invalid installer } + */ + function handleValidationStatus(statusObj) { + enableCheckForUpdateEntry(true); + UpdateStatus.cleanUpdateStatus(); + + if (statusObj.valid) { + + // Installer is validated successfully + var statusValidFn = function () { + + // Restart button click handler + var restartBtnClicked = function () { + detachUpdateBarBtnHandlers(); + initiateUpdateProcess(statusObj.installerPath, statusObj.logFilePath, statusObj.installStatusFilePath); + }; + + // Later button click handler + var laterBtnClicked = function () { + detachUpdateBarBtnHandlers(); + setUpdateStateInJSON('updateInitiatedInPrevSession', false); + }; + + //attaching UpdateBar handlers + UpdateInfoBar.on(UpdateInfoBar.RESTART_BTN_CLICKED, restartBtnClicked); + UpdateInfoBar.on(UpdateInfoBar.LATER_BTN_CLICKED, laterBtnClicked); + + UpdateInfoBar.showUpdateBar({ + title: Strings.DOWNLOAD_COMPLETE, + description: Strings.CLICK_RESTART_TO_UPDATE, + needButtons: true + }); + }; + + setUpdateStateInJSON('downloadCompleted', true, statusValidFn); + } else { + + // Installer validation failed + + if (updateJsonHandler.get("downloadCompleted")) { + + // If this was a cached download, retry downloading + updateJsonHandler.reset(); + + var statusInvalidFn = function () { + downloadAttemptsRemaining = MAX_DOWNLOAD_ATTEMPTS; + getLatestInstaller(); + }; + + setUpdateStateInJSON('downloadCompleted', false, statusInvalidFn); + } else { + + // If this is a new download, prompt the message on update bar + var descriptionMessage; + + switch (statusObj.err) { + case _nodeErrorMessages.CHECKSUM_DID_NOT_MATCH: + descriptionMessage = Strings.CHECKSUM_DID_NOT_MATCH; + break; + case _nodeErrorMessages.INSTALLER_NOT_FOUND: + descriptionMessage = Strings.INSTALLER_NOT_FOUND; + break; + } + + UpdateInfoBar.showUpdateBar({ + type: "error", + title: Strings.VALIDATION_FAILED, + description: descriptionMessage + }); + } + } + } + + /** + * Handles the download failure callback from Node + * @param {string} message - reason of download failure + */ + function handleDownloadFailure(message) { + console.log("AutoUpdate : Download of latest installer failed in Attempt " + + (MAX_DOWNLOAD_ATTEMPTS - downloadAttemptsRemaining) + ".\n Reason : " + message); + + if (downloadAttemptsRemaining) { + + // Retry the downloading + attemptToDownload(); + } else { + + // Download could not completed, all attempts exhausted + enableCheckForUpdateEntry(true); + UpdateStatus.cleanUpdateStatus(); + + var descriptionMessage; + if (message === _nodeErrorMessages.DOWNLOAD_ERROR) { + descriptionMessage = Strings.DOWNLOAD_ERROR; + } + + UpdateInfoBar.showUpdateBar({ + type: "error", + title: Strings.DOWNLOAD_FAILED, + description: descriptionMessage + }); + } + + } + + + /** + * Handles the completion of node state initialization + */ + function handleInitializationComplete() { + enableCheckForUpdateEntry(false); + getLatestInstaller(); + } + + + /** + * Handles Download completion callback from Node + */ + function handleDownloadSuccess() { + UpdateStatus.showUpdateStatus("validating-installer"); + validateChecksum(); + } + + + /** + * Generates a map for brackets side functions + */ + function registerBracketsFunctions() { + functionMap["brackets.notifyinitializationComplete"] = handleInitializationComplete; + functionMap["brackets.showStatusInfo"] = showStatusInfo; + functionMap["brackets.notifyDownloadSuccess"] = handleDownloadSuccess; + functionMap["brackets.showErrorMessage"] = showErrorMessage; + functionMap["brackets.notifyDownloadFailure"] = handleDownloadFailure; + functionMap["brackets.notifySafeToDownload"] = handleSafeToDownload; + functionMap["brackets.notifyvalidationStatus"] = handleValidationStatus; + } + + functionMap["brackets.registerBracketsFunctions"] = registerBracketsFunctions; + +}); diff --git a/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js new file mode 100644 index 00000000000..6f9538a132f --- /dev/null +++ b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ + +/*global exports */ +/*global process*/ +(function () { + "use strict"; + + var _domainManager; + + var request = require('request'), + progress = require('request-progress'), + path = require('path'), + fs = require('fs-extra'), + crypto = require('crypto'); + + // Current Date and Time needed for log filenames + var curDate = Date.now().toString(); + + //AUTOUPDATE_PRERELEASE + //Installer log file + var logFile = curDate + 'update.logs', + logFilePath; + + //Install status file + var installStatusFile = curDate + 'installStatus.logs', + installStatusFilePath; + + var updateDir, + _updateParams; + + var MessageIds, + installerPath; + + // function map for node functions + var functionMap = {}; + + var _nodeErrorMessages = { + UPDATEDIR_READ_FAILED: 0, + UPDATEDIR_CLEAN_FAILED: 1, + CHECKSUM_DID_NOT_MATCH: 2, + INSTALLER_NOT_FOUND: 3, + DOWNLOAD_ERROR: 4 + }; + + /** + * Gets the arguments to a function in an array + * @param {object} args - the arguments object + * @returns {Array} - array of actual arguments + */ + function getFunctionArgs(args) { + if (args.length > 1) { + var fnArgs = new Array(args.length - 1), + i; + for (i = 1; i < args.length; ++i) { + fnArgs[i - 1] = args[i]; + } + return fnArgs; + } + return []; + } + + + /** + * Posts messages to brackets + * @param {string} messageId - Message to be passed + */ + function postMessageToBrackets(messageId) { + var msgObj = { + fn: messageId, + args: getFunctionArgs(arguments) + }; + _domainManager.emitEvent('AutoUpdate', 'data', [msgObj]); + + } + + /** + * Quotes and Converts a file path, to accommodate platform dependent paths + * @param {string} qncPath - file path + * @param {boolean} resolve - false if path is only to be quoted, true if both quoted and converted + * @returns {string} quoted and converted file path + */ + function quoteAndConvert(qncPath, resolve) { + if (resolve) { + qncPath = path.resolve(qncPath); + } + return "\"" + qncPath + "\""; + } + + + /** + * Validates the checksum of a file against a given checksum + * @param {object} params - json containing { + * filePath - path to the file, + * expectedChecksum - the checksum to validate against } + */ + function validateChecksum(params) { + params = params || { + filePath: installerPath, + expectedChecksum: _updateParams.checksum + }; + + var hash = crypto.createHash('sha256'); + + if (fs.existsSync(params.filePath)) { + var stream = fs.createReadStream(params.filePath); + + stream.on('data', function (data) { + hash.update(data); + }); + + stream.on('end', function () { + var calculatedChecksum = hash.digest('hex'), + isValidChecksum = (params.expectedChecksum === calculatedChecksum), + status; + + if (isValidChecksum) { + if (process.platform === "darwin") { + status = { + valid: true, + installerPath: installerPath, + logFilePath: logFilePath, + installStatusFilePath: installStatusFilePath + }; + } else if (process.platform === "win32") { + status = { + valid: true, + installerPath: quoteAndConvert(installerPath, true), + logFilePath: quoteAndConvert(logFilePath, true), + installStatusFilePath: installStatusFilePath + }; + } + } else { + status = { + valid: false, + err: _nodeErrorMessages.CHECKSUM_DID_NOT_MATCH + }; + } + postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, status); + }); + } else { + var status = { + valid: false, + err: _nodeErrorMessages.INSTALLER_NOT_FOUND + }; + postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, status); + } + } + + /** + * Downloads the installer for latest Brackets release + * @param {boolean} sendInfo - true if download status info needs to be + * sent back to Brackets, false otherwise + * @param {object} [updateParams=_updateParams] - json containing update parameters + */ + function downloadInstaller(isInitialAttempt, updateParams) { + updateParams = updateParams || _updateParams; + try { + var ext = path.extname(updateParams.installerName); + var localInstallerPath = path.resolve(updateDir, Date.now().toString() + ext); + progress(request(updateParams.downloadURL), {}) + .on('progress', function (state) { + var target = "retry-download"; + if (isInitialAttempt) { + target = "initial-download"; + } + var info = Math.floor(parseFloat(state.percent) * 100).toString() + '%'; + var status = { + target: target, + spans: [{ + id: "percent", + val: info + }] + }; + postMessageToBrackets(MessageIds.SHOW_STATUS_INFO, status); + }) + .on('error', function (err) { + console.log("AutoUpdate : Download failed. Error occurred : " + err.toString()); + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, _nodeErrorMessages.DOWNLOAD_ERROR); + }) + .pipe(fs.createWriteStream(localInstallerPath)) + .on('close', function () { + fs.renameSync(localInstallerPath, installerPath); + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_SUCCESS); + }); + } catch (e) { + console.log("AutoUpdate : Download failed. Exception occurred : " + e.toString()); + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, _nodeErrorMessages.DOWNLOAD_ERROR); + } + } + + /** + * Performs clean up for the contents in Update Directory in AppData + * @param {Array} filesToCache - array of file types to cache + * @param {boolean} notifyBack - true if Brackets needs to be + * notified post cleanup, false otherwise + */ + function performCleanup(filesToCache, notifyBack) { + + function filterFilesAndNotify(files, filesToCacheArr, notifyBackToBrackets) { + files.forEach(function (file) { + var fileExt = path.extname(path.basename(file)); + if (filesToCacheArr.indexOf(fileExt) < 0) { + var fileFullPath = updateDir + '/' + file; + try { + fs.removeSync(fileFullPath); + } catch (e) { + console.log("AutoUpdate : Exception occured in removing ", fileFullPath, e); + } + } + }); + if (notifyBackToBrackets) { + postMessageToBrackets(MessageIds.NOTIFY_SAFE_TO_DOWNLOAD); + } + } + + fs.stat(updateDir) + .then(function (stats) { + if (stats) { + if (filesToCache) { + fs.readdir(updateDir) + .then(function (files) { + filterFilesAndNotify(files, filesToCache, notifyBack); + }) + .catch(function (err) { + console.log("AutoUpdate : Error in Reading Update Dir for Cleanup : " + err.toString()); + postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, + _nodeErrorMessages.UPDATEDIR_READ_FAILED); + }); + } else { + fs.remove(updateDir) + .then(function () { + console.log('AutoUpdate : Update Directory in AppData Cleaned: Complete'); + }) + .catch(function (err) { + console.log("AutoUpdate : Error in Cleaning Update Dir : " + err.toString()); + postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, + _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); + }); + } + } + }) + .catch(function (err) { + console.log("AutoUpdate : Error in Reading Update Dir stats for Cleanup : " + err.toString()); + postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, + _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); + }); + } + + /** + * Initializes the node with update parameters + * @param {object} updateParams - json containing update parameters + */ + function initializeState(updateParams) { + _updateParams = updateParams; + installerPath = path.resolve(updateDir, updateParams.installerName); + postMessageToBrackets(MessageIds.NOTIFY_INITIALIZATION_COMPLETE); + } + + + /** + * Generates a map for node side functions + */ + function registerNodeFunctions() { + functionMap["node.downloadInstaller"] = downloadInstaller; + functionMap["node.performCleanup"] = performCleanup; + functionMap["node.validateInstaller"] = validateChecksum; + functionMap["node.initializeState"] = initializeState; + } + + /** + * Initializes node for the auto update, registers messages and node side funtions + * @param {object} initObj - json containing init information { + * messageIds : Messages for brackets and node communication + * updateDir : update directory in Appdata } + */ + function initNode(initObj) { + MessageIds = initObj.messageIds; + updateDir = path.resolve(initObj.updateDir); + logFilePath = path.resolve(updateDir, logFile); + installStatusFilePath = path.resolve(updateDir, installStatusFile); + registerNodeFunctions(); + postMessageToBrackets(MessageIds.REGISTER_BRACKETS_FUNCTIONS); + } + + + /** + * Receives messages from brackets + * @param {object} msgObj - json containing - { + * fn - function to execute on node side + * args - arguments to the above function } + */ + function receiveMessageFromBrackets(msgObj) { + functionMap[msgObj.fn].apply(null, msgObj.args); + } + + /** + * Initialize the domain with commands and events related to AutoUpdate + * @param {DomainManager} domainManager - The DomainManager for AutoUpdateDomain + */ + + function init(domainManager) { + if (!domainManager.hasDomain("AutoUpdate")) { + domainManager.registerDomain("AutoUpdate", { + major: 0, + minor: 1 + }); + } + _domainManager = domainManager; + + domainManager.registerCommand( + "AutoUpdate", + "initNode", + initNode, + true, + "Initializes node for the auto update", + [ + { + name: "initObj", + type: "object", + description: "json object containing init information" + } + ], + [] + ); + + domainManager.registerCommand( + "AutoUpdate", + "data", + receiveMessageFromBrackets, + true, + "Receives messages from brackets", + [ + { + name: "msgObj", + type: "object", + description: "json object containing message info" + } + ], + [] + ); + + domainManager.registerEvent( + "AutoUpdate", + "data", + [ + { + name: "msgObj", + type: "object", + description: "json object containing message info to pass to brackets" + } + ] + ); + } + + exports.init = init; + +}()); + diff --git a/src/extensions/default/AutoUpdate/node/package.json b/src/extensions/default/AutoUpdate/node/package.json new file mode 100644 index 00000000000..6bf7cd06622 --- /dev/null +++ b/src/extensions/default/AutoUpdate/node/package.json @@ -0,0 +1,8 @@ +{ + "name": "brackets-auto-update", + "dependencies": { + "request": "^2.83.0", + "request-progress": "^3.0.0", + "fs-extra": "^5.0.0" + } +} diff --git a/src/extensions/default/AutoUpdate/styles/styles.css b/src/extensions/default/AutoUpdate/styles/styles.css new file mode 100644 index 00000000000..a404359db92 --- /dev/null +++ b/src/extensions/default/AutoUpdate/styles/styles.css @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ + +/*Status bar*/ +#update-status { + position: relative; + float: right; + padding: 0px 20px; + height: 25px; + min-width: 9%; + width: auto; + text-align: center; +} + +#update-status p { + position: relative; + display: none; + white-space: nowrap; + font-family: 'SourceSansPro'; +} + +/*Update Bar*/ +#update-bar { + display: block; + background-color: #105F9C; + box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.53); + height: 38px; + width: 100%; + position: absolute; + z-index: 15; + left: 0px; + bottom: 25px; + outline: none; + overflow: hidden; +} + +#update-bar #icon-container { + width: auto; + height: auto; + padding: 11px; + float: left; +} +#update-bar #icon-container #update-icon { + background: url("../images/info.svg") no-repeat 0 0; + width: 16px; + height: 16px; + display: block; +} + +#update-bar #content-container { + padding: 10px 7px; + float: left; + max-width: 78%; +} + +#update-bar #content-container #update-content { + margin: 0px !important; /*Check if this important is necessary*/ + line-height: 18px; + font-size: 14px; + font-family: 'SourceSansPro'; + color: #FFFFFF; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#update-bar #content-container #update-content #heading{ + font-weight: bold; +} +/*For focussed link of brackets.io*/ +#update-bar #content-container #update-content #description a:focus{ + box-shadow: none; +} + +#update-bar #content-container #update-content #description a{ + text-decoration: underline; + color: #FFFFFF; +} + +#update-bar #button-container { + display: block; + float: right; + right: 40px; + position: fixed; + background-color: #105F9C; + min-width: 180px; +} + +#update-bar #close-icon-container { + height: auto; + padding: 9px; + position: fixed; + float: right; + text-align: center; + width: auto; + min-width: 66px; + right: 30px; + background-color: #105F9C; +} + +#update-bar #close-icon-container #close-icon { + display: block; + color: white; + font-size: 18px; + line-height: 18px; + text-decoration: none; + width: 18px; + height: 18px; + background-color: transparent; + border: none; + padding: 0px; /*This is needed to center the icon*/ + float: right; +} + +#update-bar #close-icon-container #close-icon:hover { + background-color: rgba(255, 255, 255 ,0.16); + border-radius: 50%; +} + +#update-bar #close-icon-container #close-icon:focus { + background-color: rgba(255, 255, 255 ,0.16); + border-radius: 50%; + border: 1px solid #C3E3FF; + outline: 0; +} + +#update-bar #close-icon-container #close-icon:focus:active { + background-color: rgba(255, 255, 255 ,0.32); + border: none; +} + +.update-btn { + width: auto; + height: 28px; + position: relative; + float: right; + padding: 4px 15px; + border: 1px solid #EAEAEA; + font-size: 14px; + text-align: center; + font-family: 'SourceSansPro'; + color: #E6E6E6; + margin-top: 5px; + margin-right: 10px; + background-color: transparent; +} + +.update-btn:hover { + border-color: #C9C9C9; + background-color: #EAEAEA; + color: #202020; +} + +.update-btn:focus:active { + border: 1px solid #B5B5B5; + background-color: #CCCCCC; + color: #202020; + padding: 4px 15px; + box-shadow: none; +} + +.update-btn:focus { + border: 2px solid #C3E3FF; + background-color: #EAEAEA; + color: #202020; + box-shadow: 0px 3px 6px rgba(148, 206, 255, 0.23); + padding: 3px 14px; +} + +/*Warning Message in Update Bar*/ +#update-bar.warning, #update-bar.warning #close-icon-container { + background-color: #DA7A12; +} + +.dark #update-bar.warning, .dark #update-bar.warning #close-icon-container { + background-color: #E6851A; +} + +#update-bar.warning #icon-container #update-icon, +#update-bar.error #icon-container #update-icon { + background: url("../images/alert.svg") no-repeat 0 0; +} + +/*Error message in Update Bar*/ +#update-bar.error, #update-bar.error #close-icon-container { + background-color: #D7373F; +} + +.dark #update-bar.error, .dark #update-bar.error #close-icon-container{ + background-color: #E4484F; +} +/*Success message in Update Bar*/ +#update-bar.success, #update-bar.success #close-icon-container { + background-color: #278E6B; +} + +.dark #update-bar.success, .dark #update-bar.success #close-icon-container { + background-color: #2E9D77; +} + +#update-bar.success #icon-container #update-icon{ + background: url("../images/checkmarkcircle.svg") no-repeat 0 0; +} + + +/*Overrides*/ + +#status-indicators { + position: relative; + float: right; +} diff --git a/src/htmlContent/update-dialog.html b/src/htmlContent/update-dialog.html index 0355a413445..8d51905c598 100644 --- a/src/htmlContent/update-dialog.html +++ b/src/htmlContent/update-dialog.html @@ -1,9 +1,9 @@
- +
@@ -23,4 +23,4 @@
- \ No newline at end of file + diff --git a/src/project/FileSyncManager.js b/src/project/FileSyncManager.js index e8e1527a384..9230882f3a7 100644 --- a/src/project/FileSyncManager.js +++ b/src/project/FileSyncManager.js @@ -111,6 +111,8 @@ define(function (require, exports, module) { if (doc.isUntitled()) { result.resolve(); + } else if (doc.file.donotWatch) { // Some file might not like to be watched! + result.resolve(); } else { doc.file.stat(function (err, stat) { if (!err) { diff --git a/src/view/MainViewManager.js b/src/view/MainViewManager.js index 671b370795f..11841be0d94 100644 --- a/src/view/MainViewManager.js +++ b/src/view/MainViewManager.js @@ -343,9 +343,7 @@ define(function (require, exports, module) { if (!_traversingFileList) { pane.makeViewMostRecent(file); - index = _.findIndex(_mruList, function (record) { - return (record.file === file && record.paneId === pane.id); - }); + index = _findFileInMRUList(pane.id, file); entry = _makeMRUListEntry(file, pane.id); @@ -1271,7 +1269,7 @@ define(function (require, exports, module) { } }); } else { - DocumentManager.getDocumentForPath(file.fullPath) + DocumentManager.getDocumentForPath(file.fullPath, file) .done(function (doc) { if (doc) { _edit(paneId, doc, $.extend({}, options, { @@ -1288,9 +1286,9 @@ define(function (require, exports, module) { }); } - result.done(function () { - _makeFileMostRecent(paneId, file); - }); + result.done(function () { + _makeFileMostRecent(paneId, file); + }); return result; } From e0b6620f38f2f8ea20fbecf53fca033269e77f01 Mon Sep 17 00:00:00 2001 From: Sorab Bisht Date: Mon, 7 May 2018 01:37:33 -0700 Subject: [PATCH 018/149] Adding Analytics logging for JSRefactor, Live Preview, Quick Edit and more features (#14253) * Adding Analytics logging for JSRefactor, Live Preview, Quick Edit and more features * Addressing review comments --- src/LiveDevelopment/LiveDevelopment.js | 10 ++- src/editor/CSSInlineEditor.js | 9 +++ src/editor/EditorStatusBar.js | 10 ++- .../default/JavaScriptQuickEdit/main.js | 10 ++- .../default/JavaScriptRefactoring/main.js | 76 +++++++++++++++++-- .../default/MDNDocs/InlineDocsViewer.js | 20 ++++- src/extensions/default/MDNDocs/main.js | 11 ++- src/preferences/PreferencesDialogs.js | 10 ++- 8 files changed, 142 insertions(+), 14 deletions(-) diff --git a/src/LiveDevelopment/LiveDevelopment.js b/src/LiveDevelopment/LiveDevelopment.js index 93cd81f16f9..22e677ae372 100644 --- a/src/LiveDevelopment/LiveDevelopment.js +++ b/src/LiveDevelopment/LiveDevelopment.js @@ -96,7 +96,8 @@ define(function LiveDevelopment(require, exports, module) { StringUtils = require("utils/StringUtils"), UserServer = require("LiveDevelopment/Servers/UserServer").UserServer, WebSocketTransport = require("LiveDevelopment/transports/WebSocketTransport"), - PreferencesManager = require("preferences/PreferencesManager"); + PreferencesManager = require("preferences/PreferencesManager"), + HealthLogger = require("utils/HealthLogger"); // Inspector var Inspector = require("LiveDevelopment/Inspector/Inspector"); @@ -1350,6 +1351,13 @@ define(function LiveDevelopment(require, exports, module) { }); } } + // Send analytics data when Live Preview is opened + HealthLogger.sendAnalyticsData( + "livePreviewOpen", + "usage", + "livePreview", + "open" + ); // Register user defined server provider and keep handlers for further clean-up _regServers.push(LiveDevServerManager.registerServer({ create: _createUserServer }, 99)); diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index 363f722ed9c..e51a280f4d9 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -39,6 +39,7 @@ define(function (require, exports, module) { MultiRangeInlineEditor = require("editor/MultiRangeInlineEditor"), Strings = require("strings"), ViewUtils = require("utils/ViewUtils"), + HealthLogger = require("utils/HealthLogger"), _ = require("thirdparty/lodash"); var _newRuleCmd, @@ -169,6 +170,14 @@ define(function (require, exports, module) { return null; } + //Send analytics data for QuickEdit open + HealthLogger.sendAnalyticsData( + "QuickEditOpen", + "usage", + "quickEdit", + "open" + ); + // Only provide CSS editor if the selection is within a single line var sel = hostEditor.getSelection(); if (sel.start.line !== sel.end.line) { diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index d7ebbcead66..c3a53857fae 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -50,7 +50,8 @@ define(function (require, exports, module) { CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), DocumentManager = require("document/DocumentManager"), - StringUtils = require("utils/StringUtils"); + StringUtils = require("utils/StringUtils"), + HealthLogger = require("utils/HealthLogger"); var SupportedEncodingsText = require("text!supported-encodings.json"), SupportedEncodings = JSON.parse(SupportedEncodingsText); @@ -174,6 +175,13 @@ define(function (require, exports, module) { selStr = ""; if (sels.length > 1) { + //Send analytics data for multicursor use + HealthLogger.sendAnalyticsData( + "multiCursor", + "usage", + "multiCursor", + "use" + ); selStr = StringUtils.format(Strings.STATUSBAR_SELECTION_MULTIPLE, sels.length); } else if (editor.hasSelection()) { var sel = sels[0]; diff --git a/src/extensions/default/JavaScriptQuickEdit/main.js b/src/extensions/default/JavaScriptQuickEdit/main.js index 72d95ad8aea..2f1089dba48 100644 --- a/src/extensions/default/JavaScriptQuickEdit/main.js +++ b/src/extensions/default/JavaScriptQuickEdit/main.js @@ -31,7 +31,8 @@ define(function (require, exports, module) { LanguageManager = brackets.getModule("language/LanguageManager"), PerfUtils = brackets.getModule("utils/PerfUtils"), ProjectManager = brackets.getModule("project/ProjectManager"), - Strings = brackets.getModule("strings"); + Strings = brackets.getModule("strings"), + HealthLogger = brackets.getModule("utils/HealthLogger"); /** * Return the token string that is at the specified position. @@ -196,6 +197,13 @@ define(function (require, exports, module) { return null; } + //Send analytics data for Quick Edit open + HealthLogger.sendAnalyticsData( + "QuickEditOpen", + "usage", + "quickEdit", + "open" + ); // Only provide JavaScript editor if the selection is within a single line var sel = hostEditor.getSelection(); if (sel.start.line !== sel.end.line) { diff --git a/src/extensions/default/JavaScriptRefactoring/main.js b/src/extensions/default/JavaScriptRefactoring/main.js index 4bc59753072..429256fa2a4 100644 --- a/src/extensions/default/JavaScriptRefactoring/main.js +++ b/src/extensions/default/JavaScriptRefactoring/main.js @@ -33,7 +33,10 @@ define(function (require, exports, module) { ExtractToFunction = require("ExtractToFunction"), WrapSelection = require("WrapSelection"), CommandManager = brackets.getModule("command/CommandManager"), - Menus = brackets.getModule("command/Menus"); + Menus = brackets.getModule("command/Menus"), + HealthLogger = brackets.getModule("utils/HealthLogger"), + _ = brackets.getModule("thirdparty/lodash"), + EditorManager = brackets.getModule("editor/EditorManager"); var jsRefactoringEnabled = true; @@ -66,6 +69,63 @@ define(function (require, exports, module) { jsRefactoringEnabled = _isRefactoringEnabled(); }); + function _handleRefactor(functionName) { + var eventName, eventType = ""; + + switch (functionName) { + case REFACTOR_RENAME: + eventName = REFACTOR_RENAME; + eventType = "rename"; + RenameIdentifier.handleRename(); + break; + case EXTRACTTO_VARIABLE: + eventName = EXTRACTTO_VARIABLE; + eventType = "extractToVariable"; + ExtractToVariable.handleExtractToVariable(); + break; + case EXTRACTTO_FUNCTION: + eventName = EXTRACTTO_FUNCTION; + eventType = "extractToFunction"; + ExtractToFunction.handleExtractToFunction(); + break; + case REFACTORWRAPINTRYCATCH: + eventName = REFACTORWRAPINTRYCATCH; + eventType = "tryCatch"; + WrapSelection.wrapInTryCatch(); + break; + case REFACTORWRAPINCONDITION: + eventName = REFACTORWRAPINCONDITION; + eventType = "wrapInCondition"; + WrapSelection.wrapInCondition(); + break; + case REFACTORCONVERTTOARROWFN: + eventName = REFACTORCONVERTTOARROWFN; + eventType = "convertToFunction"; + WrapSelection.convertToArrowFunction(); + break; + case REFACTORCREATEGETSET: + eventName = REFACTORCREATEGETSET; + eventType = "createGetterSetter"; + WrapSelection.createGettersAndSetters(); + break; + } + if (eventName) { + var editor = EditorManager.getActiveEditor(); + + // Logging should be done only when the context is javascript + if (!editor || editor.getModeForSelection() !== "javascript") { + return; + } + // Send analytics data for js refactoring + HealthLogger.sendAnalyticsData( + eventName, + "usage", + "jsRefactor", + eventType + ); + } + } + AppInit.appReady(function () { if (jsRefactoringEnabled) { @@ -76,34 +136,34 @@ define(function (require, exports, module) { Menus.getMenu(menuLocation).addMenuDivider(); // Rename Identifier - CommandManager.register(Strings.CMD_REFACTORING_RENAME, REFACTOR_RENAME, RenameIdentifier.handleRename); + CommandManager.register(Strings.CMD_REFACTORING_RENAME, REFACTOR_RENAME, _.partial(_handleRefactor, REFACTOR_RENAME)); subMenu.addMenuItem(REFACTOR_RENAME); Menus.getMenu(menuLocation).addMenuItem(REFACTOR_RENAME, KeyboardPrefs.renameIdentifier); // Extract to Variable - CommandManager.register(Strings.CMD_EXTRACTTO_VARIABLE, EXTRACTTO_VARIABLE, ExtractToVariable.handleExtractToVariable); + CommandManager.register(Strings.CMD_EXTRACTTO_VARIABLE, EXTRACTTO_VARIABLE, _.partial(_handleRefactor, EXTRACTTO_VARIABLE)); subMenu.addMenuItem(EXTRACTTO_VARIABLE); Menus.getMenu(menuLocation).addMenuItem(EXTRACTTO_VARIABLE, KeyboardPrefs.extractToVariable); // Extract to Function - CommandManager.register(Strings.CMD_EXTRACTTO_FUNCTION, EXTRACTTO_FUNCTION, ExtractToFunction.handleExtractToFunction); + CommandManager.register(Strings.CMD_EXTRACTTO_FUNCTION, EXTRACTTO_FUNCTION, _.partial(_handleRefactor, EXTRACTTO_FUNCTION)); subMenu.addMenuItem(EXTRACTTO_FUNCTION); Menus.getMenu(menuLocation).addMenuItem(EXTRACTTO_FUNCTION, KeyboardPrefs.extractToFunction); // Wrap Selection - CommandManager.register(Strings.CMD_REFACTORING_TRY_CATCH, REFACTORWRAPINTRYCATCH, WrapSelection.wrapInTryCatch); + CommandManager.register(Strings.CMD_REFACTORING_TRY_CATCH, REFACTORWRAPINTRYCATCH, _.partial(_handleRefactor, REFACTORWRAPINTRYCATCH)); subMenu.addMenuItem(REFACTORWRAPINTRYCATCH); Menus.getMenu(menuLocation).addMenuItem(REFACTORWRAPINTRYCATCH); - CommandManager.register(Strings.CMD_REFACTORING_CONDITION, REFACTORWRAPINCONDITION, WrapSelection.wrapInCondition); + CommandManager.register(Strings.CMD_REFACTORING_CONDITION, REFACTORWRAPINCONDITION, _.partial(_handleRefactor, REFACTORWRAPINCONDITION)); subMenu.addMenuItem(REFACTORWRAPINCONDITION); Menus.getMenu(menuLocation).addMenuItem(REFACTORWRAPINCONDITION); - CommandManager.register(Strings.CMD_REFACTORING_ARROW_FUNCTION, REFACTORCONVERTTOARROWFN, WrapSelection.convertToArrowFunction); + CommandManager.register(Strings.CMD_REFACTORING_ARROW_FUNCTION, REFACTORCONVERTTOARROWFN, _.partial(_handleRefactor, REFACTORCONVERTTOARROWFN)); subMenu.addMenuItem(REFACTORCONVERTTOARROWFN); Menus.getMenu(menuLocation).addMenuItem(REFACTORCONVERTTOARROWFN); - CommandManager.register(Strings.CMD_REFACTORING_GETTERS_SETTERS, REFACTORCREATEGETSET, WrapSelection.createGettersAndSetters); + CommandManager.register(Strings.CMD_REFACTORING_GETTERS_SETTERS, REFACTORCREATEGETSET, _.partial(_handleRefactor, REFACTORCREATEGETSET)); subMenu.addMenuItem(REFACTORCREATEGETSET); Menus.getMenu(menuLocation).addMenuItem(REFACTORCREATEGETSET); } diff --git a/src/extensions/default/MDNDocs/InlineDocsViewer.js b/src/extensions/default/MDNDocs/InlineDocsViewer.js index 25372e10507..d3ab81abd9c 100644 --- a/src/extensions/default/MDNDocs/InlineDocsViewer.js +++ b/src/extensions/default/MDNDocs/InlineDocsViewer.js @@ -33,7 +33,8 @@ define(function (require, exports, module) { InlineWidget = brackets.getModule("editor/InlineWidget").InlineWidget, KeyEvent = brackets.getModule("utils/KeyEvent"), Strings = brackets.getModule("strings"), - Mustache = brackets.getModule("thirdparty/mustache/mustache"); + Mustache = brackets.getModule("thirdparty/mustache/mustache"), + HealthLogger = brackets.getModule("utils/HealthLogger"); // Load template var inlineEditorTemplate = require("text!InlineDocsViewer.html"); @@ -73,6 +74,8 @@ define(function (require, exports, module) { this.$scroller = this.$wrapperDiv.find(".scroller"); this.$scroller.on("mousewheel", this._handleWheelScroll); + this.$moreinfo = this.$wrapperDiv.find(".more-info"); + this.$moreinfo.on("click", this._logAnalyticsData); this._onKeydown = this._onKeydown.bind(this); } @@ -191,6 +194,21 @@ define(function (require, exports, module) { InlineDocsViewer.prototype._sizeEditorToContent = function () { this.hostEditor.setInlineWidgetHeight(this, this.$wrapperDiv.height() + 20, true); }; + + /** + * Send analytics data for Quick Doc "readMore" action + * + * @return {boolean} false + */ + InlineDocsViewer.prototype._logAnalyticsData = function () { + HealthLogger.sendAnalyticsData( + "QuickDocReadMore", + "usage", + "quickDoc", + "readMore" + ); + return false; + }; module.exports = InlineDocsViewer; diff --git a/src/extensions/default/MDNDocs/main.js b/src/extensions/default/MDNDocs/main.js index 356347918c2..31d123176a1 100644 --- a/src/extensions/default/MDNDocs/main.js +++ b/src/extensions/default/MDNDocs/main.js @@ -31,7 +31,8 @@ define(function (require, exports, module) { FileUtils = brackets.getModule("file/FileUtils"), CSSUtils = brackets.getModule("language/CSSUtils"), HTMLUtils = brackets.getModule("language/HTMLUtils"), - ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + HealthLogger = brackets.getModule("utils/HealthLogger"); // Extension modules var InlineDocsViewer = require("InlineDocsViewer"); @@ -102,6 +103,14 @@ define(function (require, exports, module) { return null; } + // Send analytics data for Quick Doc open + HealthLogger.sendAnalyticsData( + "cssQuickDoc", + "usage", + "quickDoc", + "open" + ); + // Only provide docs if the selection is within a single line var sel = hostEditor.getSelection(); if (sel.start.line !== sel.end.line) { diff --git a/src/preferences/PreferencesDialogs.js b/src/preferences/PreferencesDialogs.js index 65b6ddec391..8163ca2eea7 100644 --- a/src/preferences/PreferencesDialogs.js +++ b/src/preferences/PreferencesDialogs.js @@ -34,7 +34,8 @@ define(function (require, exports, module) { Strings = require("strings"), SettingsDialogTemplate = require("text!htmlContent/project-settings-dialog.html"), Mustache = require("thirdparty/mustache/mustache"), - PathUtils = require("thirdparty/path-utils/path-utils"); + PathUtils = require("thirdparty/path-utils/path-utils"), + HealthLogger = require("utils/HealthLogger"); /** * Validate that text string is a valid base url which should map to a server folder @@ -101,6 +102,13 @@ define(function (require, exports, module) { var baseUrlValue = $baseUrlControl.val(); var result = _validateBaseUrl(baseUrlValue); if (result === "") { + // Send analytics data when url is set in project settings + HealthLogger.sendAnalyticsData( + "projectSettingsLivepreview", + "usage", + "projectSettings", + "use" + ); ProjectManager.setBaseUrl(baseUrlValue); } else { // Re-invoke dialog with result (error message) From a383712e294a965c51383a51e69009465e15b577 Mon Sep 17 00:00:00 2001 From: Boopesh Mahendran Date: Tue, 8 May 2018 23:03:38 +0530 Subject: [PATCH 019/149] Fix issue 14298 (#14312) --- src/project/ProjectModel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/project/ProjectModel.js b/src/project/ProjectModel.js index a12c63f324a..cd0ded1e1d7 100644 --- a/src/project/ProjectModel.js +++ b/src/project/ProjectModel.js @@ -991,6 +991,7 @@ define(function (require, exports, module) { viewModel.renameItem(oldProjectPath, self.makeProjectRelativeIfPossible(newPath)); if (self._selections.selected && self._selections.selected.indexOf(oldPath) === 0) { self._selections.selected = newPath + self._selections.selected.slice(oldPath.length); + self.setCurrentFile(newPath); } } From 68aa861681dafec5f6d410ea52c8b66f95bb7373 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Wed, 9 May 2018 19:04:58 +0530 Subject: [PATCH 020/149] Auto Update Feature CSS Style Issue Fix (#14295) * Auto Update Feature Css Bug Fix * Adding Missed Files * Changed the caluclation of content Container Size * Added Fix for Issue #14219 and #14223 * Added missed changes * Addressed Update Status Style Issue * Addressed Review Comments * Addressed Review Comments --- .../default/AutoUpdate/UpdateInfoBar.js | 30 ++++++++++++++++++- .../default/AutoUpdate/UpdateStatus.js | 3 +- src/extensions/default/AutoUpdate/main.js | 1 + .../default/AutoUpdate/styles/styles.css | 6 ++++ src/styles/brackets.less | 3 ++ src/styles/images/updateSpritesGrayed.svg | 3 ++ src/utils/UpdateNotification.js | 14 +++++++++ 7 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/styles/images/updateSpritesGrayed.svg diff --git a/src/extensions/default/AutoUpdate/UpdateInfoBar.js b/src/extensions/default/AutoUpdate/UpdateInfoBar.js index b7b689e77dd..5244fca3079 100644 --- a/src/extensions/default/AutoUpdate/UpdateInfoBar.js +++ b/src/extensions/default/AutoUpdate/UpdateInfoBar.js @@ -28,7 +28,8 @@ define(function (require, exports, module) { Mustache = brackets.getModule("thirdparty/mustache/mustache"), EventDispatcher = brackets.getModule("utils/EventDispatcher"), UpdateBarHtml = require("text!htmlContent/updateBar.html"), - Strings = brackets.getModule("strings"); + Strings = brackets.getModule("strings"), + _ = brackets.getModule("thirdparty/lodash"); EventDispatcher.makeEventDispatcher(exports); @@ -80,6 +81,7 @@ define(function (require, exports, module) { $updateBar.remove(); } $(window.document).off("keydown.AutoUpdate"); + $(window).off('resize.AutoUpdateBar'); } /** @@ -97,6 +99,9 @@ define(function (require, exports, module) { var $updateBar = $('#update-bar'), $updateContent = $updateBar.find('#update-content'), $contentContainer = $updateBar.find('#content-container'), + $buttonContainer = $updateBar.find('#button-container'), + $iconContainer = $updateBar.find('#icon-container'), + $closeIconContainer = $updateBar.find('#close-icon-container'), $heading = $updateBar.find('#heading'), $description = $updateBar.find('#description'), $restart = $updateBar.find('#update-btn-restart'), @@ -113,6 +118,29 @@ define(function (require, exports, module) { } } } + // Content Container Width between Icon Container and Button Container or Close Icon Container + // will be assigned when window will be rezied. + var resizeContentContainer = function () { + if($updateContent.length > 0 && $contentContainer.length > 0 && $updateBar.length > 0) { + var newWidth = $updateBar.outerWidth() - 38; + if($buttonContainer.length > 0) { + newWidth = newWidth- $buttonContainer.outerWidth(); + } + if($iconContainer.length > 0) { + newWidth = newWidth - $iconContainer.outerWidth(); + } + if($closeIconContainer.length > 0) { + newWidth = newWidth - $closeIconContainer.outerWidth(); + } + + $contentContainer.css({ + "maxWidth": newWidth + }); + } + }; + + resizeContentContainer(); + $(window).on('resize.AutoUpdateBar', _.debounce(resizeContentContainer, 150)); //Event handlers on the Update Bar diff --git a/src/extensions/default/AutoUpdate/UpdateStatus.js b/src/extensions/default/AutoUpdate/UpdateStatus.js index 0d459350974..b000370f92f 100644 --- a/src/extensions/default/AutoUpdate/UpdateStatus.js +++ b/src/extensions/default/AutoUpdate/UpdateStatus.js @@ -77,7 +77,8 @@ define(function (require, exports, module) { function displayProgress(statusObj) { statusObj.spans.forEach(function (span) { if (span.id === 'percent') { - var bgval = 'linear-gradient(to right, #1474BF ' + span.val + ', rgba(0, 0, 0, 0) 0%'; + var bgColor = $('#status-bar').css('backgroundColor'), + bgval = 'linear-gradient(to right, #1474BF ' + span.val + ', ' + bgColor + ' 0%'; $('#update-status').css('background', bgval); } }); diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index 07e1943633e..1d0202c3892 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -524,6 +524,7 @@ define(function (require, exports, module) { function enableCheckForUpdateEntry(enable) { var cfuCommand = CommandManager.get(Commands.HELP_CHECK_FOR_UPDATE); cfuCommand.setEnabled(enable); + UpdateNotification.enableUpdateNotificationIcon(enable); } /** diff --git a/src/extensions/default/AutoUpdate/styles/styles.css b/src/extensions/default/AutoUpdate/styles/styles.css index a404359db92..713d99afad8 100644 --- a/src/extensions/default/AutoUpdate/styles/styles.css +++ b/src/extensions/default/AutoUpdate/styles/styles.css @@ -30,6 +30,11 @@ min-width: 9%; width: auto; text-align: center; + background: #fff; +} + +.dark #update-status { + background: #1c1c1e; } #update-status p { @@ -156,6 +161,7 @@ float: right; padding: 4px 15px; border: 1px solid #EAEAEA; + border-radius: 3px; font-size: 14px; text-align: center; font-family: 'SourceSansPro'; diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 19b12ef2e2d..c1442624866 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -754,6 +754,9 @@ a, img { #update-notification { .sprite-icon(0, 0, 24px, 24px, "images/updateSprites.svg"); + &.update-in-progress { + .sprite-icon(0, 0, 24px, 24px, "images/updateSpritesGrayed.svg"); + } } #toolbar-go-live { diff --git a/src/styles/images/updateSpritesGrayed.svg b/src/styles/images/updateSpritesGrayed.svg new file mode 100644 index 00000000000..ba162e98639 --- /dev/null +++ b/src/styles/images/updateSpritesGrayed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/utils/UpdateNotification.js b/src/utils/UpdateNotification.js index e7dfb688a52..8bef34b0ac7 100644 --- a/src/utils/UpdateNotification.js +++ b/src/utils/UpdateNotification.js @@ -372,6 +372,19 @@ define(function (require, exports, module) { } } + /** + * + * @param {boolean} enable when false Update Notification Icon in + TollBar will grayed and Click will be blocked. + when true icon will be green and clickable. + */ + function enableUpdateNotificationIcon(enable) { + var $updateNotification = $("#update-notification"); + _addedClickHandler = !enable; + + $updateNotification.toggleClass("update-in-progress", !enable); + } + /** * Check for updates. If "force" is true, update notification dialogs are always displayed * (if an update is available). If "force" is false, the update notification is only @@ -535,4 +548,5 @@ define(function (require, exports, module) { exports.resetToDefaultUpdateHandler = resetToDefaultUpdateHandler; exports.launchAutomaticUpdate = launchAutomaticUpdate; exports.checkForUpdate = checkForUpdate; + exports.enableUpdateNotificationIcon = enableUpdateNotificationIcon; }); From 19ccd5d5ad94cb19f2fa9ce5a366e5c87806c09c Mon Sep 17 00:00:00 2001 From: Boopesh Mahendran Date: Wed, 9 May 2018 19:05:48 +0530 Subject: [PATCH 021/149] Fix Issue 14313 (#14317) --- src/styles/brackets.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/brackets.less b/src/styles/brackets.less index c1442624866..0a69d9c2a47 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -1896,6 +1896,7 @@ textarea.exclusions-editor { border-radius: 0 0 4px 4px; box-shadow: 0 5px 10px @bc-shadow; opacity: 0; + overflow-wrap: break-word; .animation (autocomplete, 90ms, cubic-bezier(.01, .91, 0, .99)); -webkit-animation-fill-mode: forwards; From c4d4684bfedf2e10672a8b628fd1c0692b72480c Mon Sep 17 00:00:00 2001 From: walfgithub Date: Fri, 11 May 2018 13:30:48 +0530 Subject: [PATCH 022/149] ALF Automation (#14283) * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. --- src/nls/fr/strings.js | 2 +- src/nls/ja/strings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 5b9e1b598d5..42397b298f8 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -68,7 +68,7 @@ define({ "ERROR_RENAMING_NOT_IN_PROJECT": "Le fichier ou le répertoire ne fait pas partie du projet actuellement ouvert. Or, seuls les fichiers appartenant au projet peuvent être renommés à ce stade.", "ERROR_MOVING_FILE_TITLE": "Erreur lors du déplacement de l’élément {0}", "ERROR_MOVING_FILE": "Une erreur s’est produite lors du déplacement de l’élément {2} {0}. {1}", - "ERROR_MOVING_NOT_IN_PROJECT": "The file or directory is not part of the currently opened project. Unfortunately, only project files can be moved at this point.", + "ERROR_MOVING_NOT_IN_PROJECT": "Impossible de déplacer le fichier/dossier, car ils ne font pas partie du projet en cours.", "ERROR_DELETING_FILE_TITLE": "Erreur lors de la suppression du {0}", "ERROR_DELETING_FILE": "Une erreur s’est produite lors de la tentative de suppression du {2} {0}. {1}", "INVALID_FILENAME_TITLE": "{0} non valide", diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index dfe9bee8ddc..b56c39c3974 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -68,7 +68,7 @@ define({ "ERROR_RENAMING_NOT_IN_PROJECT": "ファイルまたはディレクトリが、現在開いているプロジェクトの一部ではありません。現時点で、プロジェクトファイルの名前のみを変更できます。", "ERROR_MOVING_FILE_TITLE": "{0} の移動エラー", "ERROR_MOVING_FILE": "{2} {0} を移動する際にエラーが発生しました。{1}", - "ERROR_MOVING_NOT_IN_PROJECT": "The file or directory is not part of the currently opened project. Unfortunately, only project files can be moved at this point.", + "ERROR_MOVING_NOT_IN_PROJECT": "現在のプロジェクトに含まれないため、ファイル/フォルダーを移動することはできません。", "ERROR_DELETING_FILE_TITLE": "{0} を削除する際にエラーが発生しました。", "ERROR_DELETING_FILE": "{2} {0} を削除する際にエラーが発生しました。{1}", "INVALID_FILENAME_TITLE": "無効な{0}", From ca5d2ae060c5f4f2dbb560728f6cd5f76a5b4c35 Mon Sep 17 00:00:00 2001 From: Naveen Choudhary Date: Sat, 12 May 2018 09:31:06 +0530 Subject: [PATCH 023/149] Correcting file name if file path ends with forward slash (#14323) --- .../default/RemoteFileAdapter/RemoteFile.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/RemoteFileAdapter/RemoteFile.js b/src/extensions/default/RemoteFileAdapter/RemoteFile.js index bf34d36f7bf..922cb80463a 100644 --- a/src/extensions/default/RemoteFileAdapter/RemoteFile.js +++ b/src/extensions/default/RemoteFileAdapter/RemoteFile.js @@ -44,6 +44,17 @@ define(function (require, exports, module) { hash: uri }); } + + function _getFileName(filePath) { + var fileName = filePath.split('/').pop(); + + if (!fileName.trim()) { + fileName = filePath.trim().slice(0, -1); + fileName = fileName.split('/').pop(); + } + + return fileName; + } /** * Model for a RemoteFile. @@ -62,7 +73,7 @@ define(function (require, exports, module) { this._path = fullPath; this._stat = _getStats(fullPath); this._id = fullPath; - this._name = fullPath.split('/').pop(); + this._name = _getFileName(fullPath); this._fileSystem = fileSystem; this.donotWatch = true; this.protocol = protocol; From 27c70538215b162e177ea3bb5930796ee2609cdd Mon Sep 17 00:00:00 2001 From: Bhavika Miglani Date: Tue, 15 May 2018 16:14:22 +0530 Subject: [PATCH 024/149] AutoUpdate : Changing callbacks to promises (#14285) * AutoUpdate : Changing callbacks to promises * Addressing review comments --- src/extensions/default/AutoUpdate/main.js | 35 ++++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index 1d0202c3892..e1b5991280d 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -357,7 +357,8 @@ define(function (require, exports, module) { }; if (updateJsonHandler.get('latestBuildNumber') !== _updateParams.latestBuildNumber) { - setUpdateStateInJSON('latestBuildNumber', _updateParams.latestBuildNumber, initNodeFn); + setUpdateStateInJSON('latestBuildNumber', _updateParams.latestBuildNumber) + .done(initNodeFn); } else { initNodeFn(); } @@ -555,21 +556,23 @@ define(function (require, exports, module) { /** * Sets the update state in updateHelper.json in Appdata - * @param {string} key - key to be set - * @param {string} value - value to be set - * @param {function} fn - the function - * to be called in case of - * successful setting of update state - */ - function setUpdateStateInJSON(key, value, fn) { - var func = fn || function () {}; + * @param {string} key - key to be set + * @param {string} value - value to be set + * @returns {$.Deferred} - a jquery promise, that is resolved with + * success or failure of state update in json file + */ + function setUpdateStateInJSON(key, value) { + var result = $.Deferred(); + updateJsonHandler.set(key, value) .done(function () { - func(); + result.resolve(); }) .fail(function () { resetStateInFailure("AutoUpdate : Could not modify updatehelper.json"); + result.reject(); }); + return result.promise(); } /** @@ -606,7 +609,8 @@ define(function (require, exports, module) { --downloadAttemptsRemaining; }; - setUpdateStateInJSON('downloadCompleted', false, downloadFn); + setUpdateStateInJSON('downloadCompleted', false) + .done(downloadFn); } /** @@ -785,7 +789,8 @@ define(function (require, exports, module) { resetStateInFailure("AutoUpdate : setUpdateParams could not be found in shell"); } }; - setUpdateStateInJSON('updateInitiatedInPrevSession', true, updateFn); + setUpdateStateInJSON('updateInitiatedInPrevSession', true) + .done(updateFn); } /** @@ -858,7 +863,8 @@ define(function (require, exports, module) { ); }; - setUpdateStateInJSON('downloadCompleted', true, statusValidFn); + setUpdateStateInJSON('downloadCompleted', true) + .done(statusValidFn); } else { // Installer validation failed @@ -873,7 +879,8 @@ define(function (require, exports, module) { getLatestInstaller(); }; - setUpdateStateInJSON('downloadCompleted', false, statusInvalidFn); + setUpdateStateInJSON('downloadCompleted', false) + .done(statusInvalidFn); } else { // If this is a new download, prompt the message on update bar From 64ffb198f57e7c425d667629d18f4f6a116e8ac3 Mon Sep 17 00:00:00 2001 From: Naveen Choudhary Date: Tue, 15 May 2018 17:04:00 +0530 Subject: [PATCH 025/149] Disabling certain working set context menu items for remote files (#14326) * Disabeling working set context menu for remote files * Address review comments * Expose a get context method in workingSetView and use it * Remove MainViewManager require --- .../default/RemoteFileAdapter/main.js | 22 ++++++++++++++++++ src/project/WorkingSetView.js | 23 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/RemoteFileAdapter/main.js b/src/extensions/default/RemoteFileAdapter/main.js index 39447ac0e10..ae4b394bf09 100644 --- a/src/extensions/default/RemoteFileAdapter/main.js +++ b/src/extensions/default/RemoteFileAdapter/main.js @@ -30,12 +30,34 @@ define(function (require, exports, module) { PathUtils = brackets.getModule("thirdparty/path-utils/path-utils"), CommandManager = brackets.getModule("command/CommandManager"), Commands = brackets.getModule("command/Commands"), + Menus = brackets.getModule("command/Menus"), + WorkingSetView = brackets.getModule("project/WorkingSetView"), RemoteFile = require("RemoteFile"); var HTTP_PROTOCOL = "http:", HTTPS_PROTOCOL = "https:"; + + /** + * Disable context menus which are not useful for remote file + */ + function _setMenuItemsVisible() { + var file = WorkingSetView.getContext(), + cMenuItems = [Commands.FILE_SAVE, Commands.FILE_RENAME, Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS]; + + if (file.constructor.name === "RemoteFile") { + cMenuItems.forEach(function (item) { + CommandManager.get(item).setEnabled(false); + }); + } else { + //Explicitly enabling save, other commands are handled by DefaultMenus.js + CommandManager.get(Commands.FILE_SAVE).setEnabled(true); + } + } AppInit.htmlReady(function () { + + Menus.getContextMenu(Menus.ContextMenuIds.WORKING_SET_CONTEXT_MENU).on("beforeContextMenuOpen", _setMenuItemsVisible); + var protocolAdapter = { priority: 0, // Default priority fileImpl: RemoteFile, diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index 4332d896fb8..42538254745 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -59,6 +59,13 @@ define(function (require, exports, module) { */ var _iconProviders = []; + /** + * The file/folder object of the current context + * @type {FileSystemEntry} + * @private + */ + var _contextEntry; + /** * Class Providers * @see {@link #addClassProvider} @@ -78,7 +85,8 @@ define(function (require, exports, module) { * @enum {number} */ var LEFT_BUTTON = 1, - MIDDLE_BUTTON = 2; + MIDDLE_BUTTON = 2, + RIGHT_BUTTON = 3; /** * Each list item in the working set stores a references to the related document in the list item's data. @@ -773,6 +781,11 @@ define(function (require, exports, module) { .always(function () { postDropCleanup(); }); + + // Set the context entry if the click is a right click + if (e.which === RIGHT_BUTTON) { + _contextEntry = sourceFile; + } } } else { // no need to refresh @@ -1489,6 +1502,13 @@ define(function (require, exports, module) { $element.addClass(provider(data)); }); } + + /** + * Gets the filesystem object for the current context in the working set. + */ + function getContext() { + return _contextEntry; + } // Public API exports.createWorkingSetViewForPane = createWorkingSetViewForPane; @@ -1496,6 +1516,7 @@ define(function (require, exports, module) { exports.addIconProvider = addIconProvider; exports.addClassProvider = addClassProvider; exports.syncSelectionIndicator = syncSelectionIndicator; + exports.getContext = getContext; // API to be used only by default extensions exports.useIconProviders = useIconProviders; From a813265d14005a315818225552d319cc89f44d55 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Tue, 15 May 2018 18:05:59 +0530 Subject: [PATCH 026/149] Adding protocol badge to remote files (#14330) * Using as class provider to add protocol badges to files in Working set view and recent files list * Fixing UI issues * Removing explicit removal of classes, as it causes valid classes to be removed. jquery addClass already has this check * Show badge even if file extension not present --- .../default/RemoteFileAdapter/main.js | 19 ++++++++++++++++- .../default/RemoteFileAdapter/styles.css | 21 +++++++++++++++++++ src/project/WorkingSetView.js | 1 - 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 src/extensions/default/RemoteFileAdapter/styles.css diff --git a/src/extensions/default/RemoteFileAdapter/main.js b/src/extensions/default/RemoteFileAdapter/main.js index ae4b394bf09..022946bc414 100644 --- a/src/extensions/default/RemoteFileAdapter/main.js +++ b/src/extensions/default/RemoteFileAdapter/main.js @@ -30,13 +30,28 @@ define(function (require, exports, module) { PathUtils = brackets.getModule("thirdparty/path-utils/path-utils"), CommandManager = brackets.getModule("command/CommandManager"), Commands = brackets.getModule("command/Commands"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + WorkingSetView = brackets.getModule("project/WorkingSetView"), Menus = brackets.getModule("command/Menus"), - WorkingSetView = brackets.getModule("project/WorkingSetView"), RemoteFile = require("RemoteFile"); var HTTP_PROTOCOL = "http:", HTTPS_PROTOCOL = "https:"; + ExtensionUtils.loadStyleSheet(module, "styles.css"); + + function protocolClassProvider(data) { + if (data.fullPath.startsWith("http://")) { + return "http"; + } + + if (data.fullPath.startsWith("https://")) { + return "https"; + } + + return ""; + } + /** * Disable context menus which are not useful for remote file */ @@ -89,6 +104,8 @@ define(function (require, exports, module) { } } ); + + WorkingSetView.addClassProvider(protocolClassProvider); }); }); diff --git a/src/extensions/default/RemoteFileAdapter/styles.css b/src/extensions/default/RemoteFileAdapter/styles.css new file mode 100644 index 00000000000..0f62bf391b2 --- /dev/null +++ b/src/extensions/default/RemoteFileAdapter/styles.css @@ -0,0 +1,21 @@ +.http a:after, +.https a:after { + margin-left: 5px; + border: 1px solid; + border-radius: 2px; + padding: 0px 5px; + font-size: 11px; + color: #adb9bd; +} + +.http a:after { + content: "http"; +} + +.https a:after { + content: "https"; +} + + + + diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index 42538254745..8babb4648cc 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -1120,7 +1120,6 @@ define(function (require, exports, module) { data = {fullPath: file.fullPath, name: file.name, isFile: file.isFile}; - $li.removeAttr("class"); _classProviders.forEach(function (provider) { $li.addClass(provider(data)); }); From 79b7a7837a44a7d1db20f9576a1ad2ce0b96cddc Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Tue, 15 May 2018 18:22:04 +0530 Subject: [PATCH 027/149] Make remote files read only (#14332) --- src/document/Document.js | 1 + src/document/DocumentCommandHandlers.js | 6 ++++-- src/editor/Editor.js | 2 +- src/extensions/default/RemoteFileAdapter/RemoteFile.js | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index fce97e4cf1a..a013008bb0e 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -73,6 +73,7 @@ define(function (require, exports, module) { */ function Document(file, initialTimestamp, rawText) { this.file = file; + this.editable = !file.readOnly; this._updateLanguage(); this.refreshText(rawText, initialTimestamp, true); // List of full editors which are initialized as master editors for this doc. diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index a37e22bb96f..618ada5adca 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -157,7 +157,9 @@ define(function (require, exports, module) { function _updateTitle() { var currentDoc = DocumentManager.getCurrentDocument(), windowTitle = brackets.config.app_title, - currentlyViewedPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE); + currentlyViewedFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE), + currentlyViewedPath = currentlyViewedFile.fullPath, + readOnlyString = currentlyViewedFile.readOnly ? "[Read Only] - " : ""; if (!brackets.nativeMenus) { if (currentlyViewedPath) { @@ -197,7 +199,7 @@ define(function (require, exports, module) { var projectName = projectRoot.name; // Construct shell/browser window title, e.g. "• index.html (myProject) — Brackets" if (currentlyViewedPath) { - windowTitle = StringUtils.format(WINDOW_TITLE_STRING_DOC, _currentTitlePath, projectName, brackets.config.app_title); + windowTitle = StringUtils.format(WINDOW_TITLE_STRING_DOC, readOnlyString + _currentTitlePath, projectName, brackets.config.app_title); // Display dirty dot when there are unsaved changes if (currentDoc && currentDoc.isDirty) { windowTitle = "• " + windowTitle; diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 03e7916d283..3a009056ad4 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -323,7 +323,7 @@ define(function (require, exports, module) { function Editor(document, makeMasterEditor, container, range, options) { var self = this; - var isReadOnly = options && options.isReadOnly; + var isReadOnly = (options && options.isReadOnly) || !document.editable; _instances.push(this); diff --git a/src/extensions/default/RemoteFileAdapter/RemoteFile.js b/src/extensions/default/RemoteFileAdapter/RemoteFile.js index 922cb80463a..4baf4529edd 100644 --- a/src/extensions/default/RemoteFileAdapter/RemoteFile.js +++ b/src/extensions/default/RemoteFileAdapter/RemoteFile.js @@ -70,6 +70,7 @@ define(function (require, exports, module) { function RemoteFile(protocol, fullPath, fileSystem) { this._isFile = true; this._isDirectory = false; + this.readOnly = true; this._path = fullPath; this._stat = _getStats(fullPath); this._id = fullPath; From 551b801736ae32f9f021877b9ed499c19d12ce46 Mon Sep 17 00:00:00 2001 From: walfgithub Date: Wed, 16 May 2018 11:58:07 +0530 Subject: [PATCH 028/149] ALF Automation (#14325) * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. --- src/nls/ja/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index b56c39c3974..48ca3dafcf6 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -66,7 +66,7 @@ define({ "ERROR_RENAMING_FILE_TITLE": "{0} の名前を変更する際にエラーが発生しました。", "ERROR_RENAMING_FILE": "{2} {0} の名前を変更する際にエラーが発生しました。{1}", "ERROR_RENAMING_NOT_IN_PROJECT": "ファイルまたはディレクトリが、現在開いているプロジェクトの一部ではありません。現時点で、プロジェクトファイルの名前のみを変更できます。", - "ERROR_MOVING_FILE_TITLE": "{0} の移動エラー", + "ERROR_MOVING_FILE_TITLE": "{0}の移動エラー", "ERROR_MOVING_FILE": "{2} {0} を移動する際にエラーが発生しました。{1}", "ERROR_MOVING_NOT_IN_PROJECT": "現在のプロジェクトに含まれないため、ファイル/フォルダーを移動することはできません。", "ERROR_DELETING_FILE_TITLE": "{0} を削除する際にエラーが発生しました。", From dcfe5792f60b77f5acaa60a632515535db751b76 Mon Sep 17 00:00:00 2001 From: Boopesh Mahendran Date: Thu, 17 May 2018 16:04:06 +0530 Subject: [PATCH 029/149] Set the context entry on context menu event (#14336) * Set the context entry on context menu event * Remove unwanted constant --- src/project/WorkingSetView.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index 8babb4648cc..846b9003ed7 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -85,8 +85,7 @@ define(function (require, exports, module) { * @enum {number} */ var LEFT_BUTTON = 1, - MIDDLE_BUTTON = 2, - RIGHT_BUTTON = 3; + MIDDLE_BUTTON = 2; /** * Each list item in the working set stores a references to the related document in the list item's data. @@ -781,11 +780,6 @@ define(function (require, exports, module) { .always(function () { postDropCleanup(); }); - - // Set the context entry if the click is a right click - if (e.which === RIGHT_BUTTON) { - _contextEntry = sourceFile; - } } } else { // no need to refresh @@ -1397,6 +1391,7 @@ define(function (require, exports, module) { this.$openFilesContainer.css("overflow-x", "hidden"); this.$openFilesContainer.on("contextmenu.workingSetView", function (e) { + _contextEntry = $(e.target).closest("li").data(_FILE_KEY); Menus.getContextMenu(Menus.ContextMenuIds.WORKING_SET_CONTEXT_MENU).open(e); }); From 8d951655f3ac1e3d2bdf62af4791323e53dbc7cf Mon Sep 17 00:00:00 2001 From: Naveen Choudhary Date: Fri, 18 May 2018 16:25:20 +0530 Subject: [PATCH 030/149] Fixing issue 14163, find and replace replaces all content of file when replacing new line (#14175) --- src/search/FindInFiles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index 6249257de74..83ca21a6853 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -148,7 +148,7 @@ define(function (require, exports, module) { while ((match = queryExpr.exec(contents)) !== null) { lineNum = StringUtils.offsetToLineNum(lines, match.index); line = lines[lineNum]; - ch = match.index - contents.lastIndexOf("\n", match.index) - 1; // 0-based index + ch = match.index - contents.lastIndexOf("\n", match.index - 1) - 1; // 0-based index matchedLines = match[0].split("\n"); numMatchedLines = matchedLines.length; totalMatchLength = match[0].length; From 7cb6b2c56e61539d2af2d9948419001da5e22af2 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Mon, 21 May 2018 18:31:26 +0530 Subject: [PATCH 031/149] Auto Update Bug Fixes (#14329) * Auto Update Bug Fix * Added Fix for Bug #14234 * Added Fix for #14234 * Changed the timeout Value to 3 mins * Addressed Review Comments * Removed Analytics Change * Addressed Review Comments * Addressed Review Comments --- src/extensions/default/AutoUpdate/main.js | 17 +++++----- .../AutoUpdate/node/AutoUpdateDomain.js | 31 ++++++++++++++----- src/nls/root/strings.js | 1 + 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index e1b5991280d..8b675d3ec6d 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -69,7 +69,8 @@ define(function (require, exports, module) { AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_LATER: "DownloadCompletedAndUserClickedLater", AUTOUPDATE_DOWNLOADCOMPLETE_UPDATE_BAR_RENDERED: "DownloadCompleteUpdateBarRendered", AUTOUPDATE_INSTALLATION_FAILED: "InstallationFailed", - AUTOUPDATE_INSTALLATION_SUCCESS: "InstallationSuccess" + AUTOUPDATE_INSTALLATION_SUCCESS: "InstallationSuccess", + AUTOUPDATE_CLEANUP_FAILED: "AutoUpdateCleanUpFailed" }; // function map for brackets functions @@ -85,7 +86,8 @@ define(function (require, exports, module) { UPDATEDIR_CLEAN_FAILED: 1, CHECKSUM_DID_NOT_MATCH: 2, INSTALLER_NOT_FOUND: 3, - DOWNLOAD_ERROR: 4 + DOWNLOAD_ERROR: 4, + NETWORK_SLOW_OR_DISCONNECTED: 5 }; @@ -628,6 +630,8 @@ define(function (require, exports, module) { if (checkIfOnline()) { postMessageToNode(MessageIds.PERFORM_CLEANUP, ['.json'], true); } else { + enableCheckForUpdateEntry(true); + UpdateStatus.cleanUpdateStatus(); UpdateInfoBar.showUpdateBar({ type: "warning", title: Strings.DOWNLOAD_FAILED, @@ -697,12 +701,7 @@ define(function (require, exports, module) { descriptionMessage = Strings.UPDATEDIR_CLEAN_FAILED; break; } - - UpdateInfoBar.showUpdateBar({ - type: "error", - title: Strings.CLEANUP_FAILED, - description: descriptionMessage - }); + console.log("AutoUpdate : Clean-up failed! Reason : " + descriptionMessage + ".\n"); } @@ -931,6 +930,8 @@ define(function (require, exports, module) { var descriptionMessage = ""; if (message === _nodeErrorMessages.DOWNLOAD_ERROR) { descriptionMessage = Strings.DOWNLOAD_ERROR; + } else if (message === _nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED) { + descriptionMessage = Strings.NETWORK_SLOW_OR_DISCONNECTED; } HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED, diff --git a/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js index fdf435a258d..0556c4848c5 100644 --- a/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js +++ b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js @@ -60,7 +60,8 @@ UPDATEDIR_CLEAN_FAILED: 1, CHECKSUM_DID_NOT_MATCH: 2, INSTALLER_NOT_FOUND: 3, - DOWNLOAD_ERROR: 4 + DOWNLOAD_ERROR: 4, + NETWORK_SLOW_OR_DISCONNECTED: 5 }; /** @@ -260,8 +261,11 @@ updateParams = updateParams || _updateParams; try { var ext = path.extname(updateParams.installerName); - var localInstallerPath = path.resolve(updateDir, Date.now().toString() + ext); - progress(request(updateParams.downloadURL), {}) + var localInstallerPath = path.resolve(updateDir, Date.now().toString() + ext), + localInstallerFile = fs.createWriteStream(localInstallerPath), + requestCompleted = true, + readTimeOut = 180000; + progress(request(updateParams.downloadURL, {timeout: readTimeOut}), {}) .on('progress', function (state) { var target = "retry-download"; if (isInitialAttempt) { @@ -279,12 +283,25 @@ }) .on('error', function (err) { console.log("AutoUpdate : Download failed. Error occurred : " + err.toString()); - postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, _nodeErrorMessages.DOWNLOAD_ERROR); + requestCompleted = false; + localInstallerFile.end(); + var error = err.code === 'ESOCKETTIMEDOUT' || err.code === 'ENOTFOUND' ? + _nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED : + _nodeErrorMessages.DOWNLOAD_ERROR; + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, error); }) - .pipe(fs.createWriteStream(localInstallerPath)) + .pipe(localInstallerFile) .on('close', function () { - fs.renameSync(localInstallerPath, installerPath); - postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_SUCCESS); + if (requestCompleted) { + try { + fs.renameSync(localInstallerPath, installerPath); + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_SUCCESS); + } catch (e) { + console.log("AutoUpdate : Download failed. Exception occurred : " + e.toString()); + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, + _nodeErrorMessages.DOWNLOAD_ERROR); + } + } }); } catch (e) { console.log("AutoUpdate : Download failed. Exception occurred : " + e.toString()); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 5a9503f252b..85622c529d4 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -865,6 +865,7 @@ define({ "CHECKSUM_DID_NOT_MATCH" : "Checksum didn't match.", "INSTALLER_NOT_FOUND" : "Installer not found.", "DOWNLOAD_ERROR" : "Error occurred while downloading.", + "NETWORK_SLOW_OR_DISCONNECTED" : "Network is Disconnected or too slow.", "RESTART_BUTTON" : "Restart", "LATER_BUTTON" : "Later", "DESCRIPTION_AUTO_UPDATE" : "Enable/disable Brackets Auto-update", From 0d4247473ba5bd3d70eac2e25e1bff1416c08a99 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Tue, 22 May 2018 14:56:57 +0530 Subject: [PATCH 032/149] Adding AutoUpdate Analytics Data (#14348) * Adding AutoUpdate Analytics Data * Removed Localization of Strings sent as part of Analytics Data * Addressed Review Comments --- src/extensions/default/AutoUpdate/main.js | 36 ++++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index 8b675d3ec6d..8bd7f42ed24 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -632,6 +632,13 @@ define(function (require, exports, module) { } else { enableCheckForUpdateEntry(true); UpdateStatus.cleanUpdateStatus(); + HealthLogger.sendAnalyticsData( + autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED, + "autoUpdate", + "download", + "fail", + "No Internet connection available." + ); UpdateInfoBar.showUpdateBar({ type: "warning", title: Strings.DOWNLOAD_FAILED, @@ -691,17 +698,24 @@ define(function (require, exports, module) { * @param {string} message - error string */ function showErrorMessage(message) { - var descriptionMessage; + var analyticsDescriptionMessage = ""; switch (message) { case _nodeErrorMessages.UPDATEDIR_READ_FAILED: - descriptionMessage = Strings.UPDATEDIR_READ_FAILED; + analyticsDescriptionMessage = "Update directory could not be read."; break; case _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED: - descriptionMessage = Strings.UPDATEDIR_CLEAN_FAILED; + analyticsDescriptionMessage = "Update directory could not be cleaned."; break; } - console.log("AutoUpdate : Clean-up failed! Reason : " + descriptionMessage + ".\n"); + console.log("AutoUpdate : Clean-up failed! Reason : " + analyticsDescriptionMessage + ".\n"); + HealthLogger.sendAnalyticsData( + autoUpdateEventNames.AUTOUPDATE_CLEANUP_FAILED, + "autoUpdate", + "cleanUp", + "fail", + analyticsDescriptionMessage + ); } @@ -883,14 +897,17 @@ define(function (require, exports, module) { } else { // If this is a new download, prompt the message on update bar - var descriptionMessage = ""; + var descriptionMessage = "", + analyticsDescriptionMessage = ""; switch (statusObj.err) { case _nodeErrorMessages.CHECKSUM_DID_NOT_MATCH: descriptionMessage = Strings.CHECKSUM_DID_NOT_MATCH; + analyticsDescriptionMessage = "Checksum didn't match."; break; case _nodeErrorMessages.INSTALLER_NOT_FOUND: descriptionMessage = Strings.INSTALLER_NOT_FOUND; + analyticsDescriptionMessage = "Installer not found."; break; } HealthLogger.sendAnalyticsData( @@ -898,7 +915,7 @@ define(function (require, exports, module) { "autoUpdate", "download", "fail", - descriptionMessage + analyticsDescriptionMessage ); UpdateInfoBar.showUpdateBar({ type: "error", @@ -927,18 +944,21 @@ define(function (require, exports, module) { enableCheckForUpdateEntry(true); UpdateStatus.cleanUpdateStatus(); - var descriptionMessage = ""; + var descriptionMessage = "", + analyticsDescriptionMessage = ""; if (message === _nodeErrorMessages.DOWNLOAD_ERROR) { descriptionMessage = Strings.DOWNLOAD_ERROR; + analyticsDescriptionMessage = "Error occurred while downloading."; } else if (message === _nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED) { descriptionMessage = Strings.NETWORK_SLOW_OR_DISCONNECTED; + analyticsDescriptionMessage = "Network is Disconnected or too slow."; } HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED, "autoUpdate", "download", "fail", - descriptionMessage + analyticsDescriptionMessage ); UpdateInfoBar.showUpdateBar({ type: "error", From 37793e7e135697800f2062cff18a53e207a82bcc Mon Sep 17 00:00:00 2001 From: Boopesh Mahendran Date: Wed, 23 May 2018 18:55:38 +0530 Subject: [PATCH 033/149] Fix Issue 14346 - Disable commands on File Change (#14352) * Disable menu items on file change * Refactor the enable/disable logic --- src/extensions/default/RemoteFileAdapter/main.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/extensions/default/RemoteFileAdapter/main.js b/src/extensions/default/RemoteFileAdapter/main.js index 022946bc414..143e7ff3c75 100644 --- a/src/extensions/default/RemoteFileAdapter/main.js +++ b/src/extensions/default/RemoteFileAdapter/main.js @@ -32,6 +32,7 @@ define(function (require, exports, module) { Commands = brackets.getModule("command/Commands"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), WorkingSetView = brackets.getModule("project/WorkingSetView"), + MainViewManager = brackets.getModule("view/MainViewManager"), Menus = brackets.getModule("command/Menus"), RemoteFile = require("RemoteFile"); @@ -56,22 +57,20 @@ define(function (require, exports, module) { * Disable context menus which are not useful for remote file */ function _setMenuItemsVisible() { - var file = WorkingSetView.getContext(), - cMenuItems = [Commands.FILE_SAVE, Commands.FILE_RENAME, Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS]; + var file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE), + cMenuItems = [Commands.FILE_SAVE, Commands.FILE_RENAME, Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS], + enable = (file.constructor.name !== "RemoteFile"); - if (file.constructor.name === "RemoteFile") { + // Enable or disable commands based on whether the file is a remoteFile or not. cMenuItems.forEach(function (item) { - CommandManager.get(item).setEnabled(false); + CommandManager.get(item).setEnabled(enable); }); - } else { - //Explicitly enabling save, other commands are handled by DefaultMenus.js - CommandManager.get(Commands.FILE_SAVE).setEnabled(true); - } } AppInit.htmlReady(function () { Menus.getContextMenu(Menus.ContextMenuIds.WORKING_SET_CONTEXT_MENU).on("beforeContextMenuOpen", _setMenuItemsVisible); + MainViewManager.on("currentFileChange", _setMenuItemsVisible); var protocolAdapter = { priority: 0, // Default priority From 7326f0b19342cc32cedb107761678aec8b8ea6e7 Mon Sep 17 00:00:00 2001 From: niteskum Date: Mon, 28 May 2018 14:16:45 +0530 Subject: [PATCH 034/149] Fix for Localization Bug and Retaining the Installer Log --- src/extensions/default/AutoUpdate/UpdateStatus.js | 8 ++++++-- .../default/AutoUpdate/htmlContent/updateStatus.html | 2 +- src/extensions/default/AutoUpdate/main.js | 3 +-- src/nls/root/strings.js | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/extensions/default/AutoUpdate/UpdateStatus.js b/src/extensions/default/AutoUpdate/UpdateStatus.js index b000370f92f..5508add4bd4 100644 --- a/src/extensions/default/AutoUpdate/UpdateStatus.js +++ b/src/extensions/default/AutoUpdate/UpdateStatus.js @@ -27,7 +27,8 @@ define(function (require, exports, module) { var UpdateStatusHtml = require("text!htmlContent/updateStatus.html"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), Mustache = brackets.getModule("thirdparty/mustache/mustache"), - Strings = brackets.getModule("strings"); + Strings = brackets.getModule("strings"), + StringUtils = brackets.getModule("utils/StringUtils"); ExtensionUtils.loadStyleSheet(module, "styles/styles.css"); @@ -47,6 +48,8 @@ define(function (require, exports, module) { var $updateStatus = $(Mustache.render(UpdateStatusHtml, {"Strings": Strings})); $updateStatus.appendTo('#status-bar'); + var valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, 0); + $('#update-status #' + id + ' #' + 'percent').text(valStr); $('#update-status #' + id).show(); } @@ -61,7 +64,8 @@ define(function (require, exports, module) { */ function modifyUpdateStatus(statusObj) { statusObj.spans.forEach(function (span) { - $('#update-status #' + statusObj.target + ' #' + span.id).text(span.val); + var valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, span.val.split('%')[0]); + $('#update-status #' + statusObj.target + ' #' + span.id).text(valStr); }); } diff --git a/src/extensions/default/AutoUpdate/htmlContent/updateStatus.html b/src/extensions/default/AutoUpdate/htmlContent/updateStatus.html index a7de44f3c3f..88c645f0e16 100644 --- a/src/extensions/default/AutoUpdate/htmlContent/updateStatus.html +++ b/src/extensions/default/AutoUpdate/htmlContent/updateStatus.html @@ -1,5 +1,5 @@
-

{{Strings.INITIAL_DOWNLOAD}}0%

+

{{Strings.INITIAL_DOWNLOAD}}

{{Strings.RETRY_DOWNLOAD}}1/5

{{Strings.VALIDATING_INSTALLER}}

diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index 8bd7f42ed24..474a72e1f85 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -181,7 +181,7 @@ define(function (require, exports, module) { * Checks and handles the update success and failure scenarios */ function checkUpdateStatus() { - var filesToCache = null, + var filesToCache = ['.logs'], downloadCompleted = updateJsonHandler.get("downloadCompleted"), updateInitiatedInPrevSession = updateJsonHandler.get("updateInitiatedInPrevSession"); @@ -204,7 +204,6 @@ define(function (require, exports, module) { } else { // We get here if the update started but failed checkInstallationStatus(); - filesToCache = ['.logs']; //AUTOUPDATE_PRERELEASE UpdateInfoBar.showUpdateBar({ type: "error", title: Strings.UPDATE_FAILED, diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 85622c529d4..6bc80b12a84 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -870,6 +870,7 @@ define({ "LATER_BUTTON" : "Later", "DESCRIPTION_AUTO_UPDATE" : "Enable/disable Brackets Auto-update", + "NUMBER_WITH_PERCENTAGE" : "{0}%", // Strings for Related Files "CMD_FIND_RELATED_FILES" : "Find Related Files" }); From 6022b48a60dcde2d0ea7db80fbef01d0d129798b Mon Sep 17 00:00:00 2001 From: Bhavika Miglani Date: Mon, 28 May 2018 14:53:13 +0530 Subject: [PATCH 035/149] Fixing multiple windows issues in AutoUpdate (#14320) * Fixing multiple windows issues in AutoUpdate * Addressing review comments, fixing unit testing issues * Added Fix for Multiple Window Issue * Addressed Review Comments * Addressed Review Comments * Addressed Review Comments --- .../default/AutoUpdate/MessageIds.js | 2 + .../default/AutoUpdate/StateHandler.js | 28 ++- src/extensions/default/AutoUpdate/main.js | 223 +++++++++++++----- .../AutoUpdate/node/AutoUpdateDomain.js | 116 +++++---- src/nls/root/strings.js | 4 +- 5 files changed, 275 insertions(+), 98 deletions(-) diff --git a/src/extensions/default/AutoUpdate/MessageIds.js b/src/extensions/default/AutoUpdate/MessageIds.js index c4f08129499..b969aab6150 100644 --- a/src/extensions/default/AutoUpdate/MessageIds.js +++ b/src/extensions/default/AutoUpdate/MessageIds.js @@ -29,6 +29,7 @@ define(function (require, exports, module) { exports.VALIDATE_INSTALLER = "node.validateInstaller"; exports.INITIALIZE_STATE = "node.initializeState"; exports.CHECK_INSTALLER_STATUS = "node.checkInstallerStatus"; + exports.REMOVE_FROM_REQUESTERS = "node.removeFromRequesters"; exports.SHOW_STATUS_INFO = "brackets.showStatusInfo"; exports.NOTIFY_DOWNLOAD_SUCCESS = "brackets.notifyDownloadSuccess"; exports.SHOW_ERROR_MESSAGE = "brackets.showErrorMessage"; @@ -37,5 +38,6 @@ define(function (require, exports, module) { exports.NOTIFY_INITIALIZATION_COMPLETE = "brackets.notifyinitializationComplete"; exports.NOTIFY_VALIDATION_STATUS = "brackets.notifyvalidationStatus"; exports.NOTIFY_INSTALLATION_STATUS = "brackets.notifyInstallationStatus"; + exports.SET_UPDATE_IN_PROGRESS_STATE = "brackets.setAutoUpdateInProgress"; exports.REGISTER_BRACKETS_FUNCTIONS = "brackets.registerBracketsFunctions"; }); diff --git a/src/extensions/default/AutoUpdate/StateHandler.js b/src/extensions/default/AutoUpdate/StateHandler.js index 533368890e1..8cb9e5beae1 100644 --- a/src/extensions/default/AutoUpdate/StateHandler.js +++ b/src/extensions/default/AutoUpdate/StateHandler.js @@ -125,13 +125,39 @@ define(function (require, exports, module) { StateHandler.prototype.get = function (key) { var retval = null; - if (this.state && this.state[key]) { + if (this.state && this.state[key] !== undefined) { retval = this.state[key]; } return retval; }; + /** + * Reads the value of a key from a json file. + * @param {string} key - key for which the value is to be read + * @returns {$.Deferred} - a jquery deferred promise, that is resolved + * with the read value or read failure. + */ + StateHandler.prototype.refresh = function () { + var result = $.Deferred(), + self = this; + + self.parse() + .done(function () { + if (self.state) { + result.resolve(); + } else { + result.reject(); + console.warn("AutoUpdate : updateHelper.json could not be read"); + } + }) + .fail(function (error) { + result.reject(); + console.error("AutoUpdate : updateHelper.json could not be parsed", error); + }); + + return result.promise(); + }; /** * Performs the write of JSON object to a file. diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index 8bd7f42ed24..e10e5b9b1f7 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -27,6 +27,7 @@ define(function (require, exports, module) { "use strict"; var CommandManager = brackets.getModule("command/CommandManager"), + ProjectManager = brackets.getModule("project/ProjectManager"), Commands = brackets.getModule("command/Commands"), AppInit = brackets.getModule("utils/AppInit"), UpdateNotification = brackets.getModule("utils/UpdateNotification"), @@ -46,7 +47,8 @@ define(function (require, exports, module) { var _modulePath = FileUtils.getNativeModuleDirectoryPath(module), _nodePath = "node/AutoUpdateDomain", _domainPath = [_modulePath, _nodePath].join("/"), - updateDomain; + updateDomain, + domainID; var appSupportDirectory = brackets.app.getApplicationSupportDirectory(), updateDir = appSupportDirectory + '/updateTemp', @@ -90,6 +92,34 @@ define(function (require, exports, module) { NETWORK_SLOW_OR_DISCONNECTED: 5 }; + var updateProgressKey = "autoUpdateInProgress", + isAutoUpdateInitiated = false; + + /** + * Checks if an auto update session is currently in progress + * @returns {boolean} - true if an auto update session is currently in progress, false otherwise + */ + function checkIfAnotherSessionInProgress() { + var result = $.Deferred(); + if(updateJsonHandler) { + var state = updateJsonHandler.state; + + updateJsonHandler.refresh() + .done(function() { + var val = updateJsonHandler.get(updateProgressKey); + if(val !== null) { + result.resolve(val); + } else { + result.reject(); + } + }) + .fail(function() { + updateJsonHandler.state = state; + result.reject(); + }); + } + return result.promise(); + } /** * Checks if auto update preference is enabled or disabled @@ -106,9 +136,14 @@ define(function (require, exports, module) { * @param {object} msgObj - json containing - { * fn - function to execute on Brackets side * args - arguments to the above function + * requester - ID of the current requester domain */ function receiveMessageFromNode(event, msgObj) { - functionMap[msgObj.fn].apply(null, msgObj.args); + if (functionMap[msgObj.fn]) { + if(domainID === msgObj.requester) { + functionMap[msgObj.fn].apply(null, msgObj.args); + } + } } /* @@ -150,7 +185,8 @@ define(function (require, exports, module) { function postMessageToNode(messageId) { var msg = { fn: messageId, - args: getFunctionArgs(arguments) + args: getFunctionArgs(arguments), + requester: domainID }; updateDomain.exec('data', msg); } @@ -248,8 +284,16 @@ define(function (require, exports, module) { */ function initState() { updateJsonHandler.parse() - .done(function () { - checkUpdateStatus(); + .done(function() { + checkIfAnotherSessionInProgress() + .done(function (inProgress) { + if (!inProgress) { + checkUpdateStatus(); + } + }) + .fail(function () { + checkUpdateStatus(); + }); }) .fail(function (code) { var logMsg; @@ -281,7 +325,8 @@ define(function (require, exports, module) { updateDomain.exec('initNode', { messageIds: MessageIds, - updateDir: updateDir + updateDir: updateDir, + requester: domainID }); updateDomain.on('data', receiveMessageFromNode); @@ -354,16 +399,41 @@ define(function (require, exports, module) { initializeState() .done(function () { - var initNodeFn = function () { - postMessageToNode(MessageIds.INITIALIZE_STATE, _updateParams); + + var setUpdateInProgress = function() { + var initNodeFn = function () { + isAutoUpdateInitiated = true; + postMessageToNode(MessageIds.INITIALIZE_STATE, _updateParams); + }; + + if (updateJsonHandler.get('latestBuildNumber') !== _updateParams.latestBuildNumber) { + setUpdateStateInJSON('latestBuildNumber', _updateParams.latestBuildNumber) + .done(initNodeFn); + } else { + initNodeFn(); + } }; - if (updateJsonHandler.get('latestBuildNumber') !== _updateParams.latestBuildNumber) { - setUpdateStateInJSON('latestBuildNumber', _updateParams.latestBuildNumber) - .done(initNodeFn); - } else { - initNodeFn(); - } + checkIfAnotherSessionInProgress() + .done(function(inProgress) { + if(inProgress) { + UpdateInfoBar.showUpdateBar({ + type: "error", + title: Strings.AUTOUPDATE_ERROR, + description: Strings.AUTOUPDATE_IN_PROGRESS + }); + } else { + setUpdateStateInJSON("autoUpdateInProgress", true) + .done(setUpdateInProgress); + + } + }) + .fail(function() { + setUpdateStateInJSON("autoUpdateInProgress", true) + .done(setUpdateInProgress); + + }); + }) .fail(function () { UpdateInfoBar.showUpdateBar({ @@ -397,47 +467,48 @@ define(function (require, exports, module) { */ function _updateProcessHandler(updates) { - if(!updates) { - console.warn("AutoUpdate : updates information not available."); - return; - } - var OS = getPlatformInfo(), - checksum, - downloadURL, - installerName, - platforms, - latestUpdate; + if (!updates) { + console.warn("AutoUpdate : updates information not available."); + return; + } + var OS = getPlatformInfo(), + checksum, + downloadURL, + installerName, + platforms, + latestUpdate; - latestUpdate = updates[0]; - platforms = latestUpdate ? latestUpdate.platforms : null; + latestUpdate = updates[0]; + platforms = latestUpdate ? latestUpdate.platforms : null; - if(platforms && platforms[OS]) { + if (platforms && platforms[OS]) { - //If no checksum field is present then we're setting it to 0, just as a safety check, - // although ideally this situation should never occur in releases post its introduction. - checksum = platforms[OS].checksum ? platforms[OS].checksum : 0, - downloadURL = platforms[OS].downloadURL ? platforms[OS].downloadURL : "", - installerName = downloadURL ? downloadURL.split("/").pop() : ""; + //If no checksum field is present then we're setting it to 0, just as a safety check, + // although ideally this situation should never occur in releases post its introduction. + checksum = platforms[OS].checksum ? platforms[OS].checksum : 0, + downloadURL = platforms[OS].downloadURL ? platforms[OS].downloadURL : "", + installerName = downloadURL ? downloadURL.split("/").pop() : ""; - } else { - // Update not present for current platform - return; - } + } else { + // Update not present for current platform + return; + } - if (!checksum || !downloadURL || !installerName) { - console.warn("AutoUpdate : asset information incorrect for the update"); - return; - } + if (!checksum || !downloadURL || !installerName) { + console.warn("AutoUpdate : asset information incorrect for the update"); + return; + } - var updateParams = { - downloadURL: downloadURL, - installerName: installerName, - latestBuildNumber: latestUpdate.buildNumber, - checksum: checksum - }; + var updateParams = { + downloadURL: downloadURL, + installerName: installerName, + latestBuildNumber: latestUpdate.buildNumber, + checksum: checksum + }; - //Initiate the auto update, with update params - initiateAutoUpdate(updateParams); + + //Initiate the auto update, with update params + initiateAutoUpdate(updateParams); } @@ -510,6 +581,7 @@ define(function (require, exports, module) { .done(function () { setupAutoUpdatePreference(); if (_isAutoUpdateEnabled()) { + domainID = (new Date()).getTime().toString(); setupAutoUpdate(); UpdateNotification.registerUpdateHandler(_updateProcessHandler); } @@ -520,6 +592,16 @@ define(function (require, exports, module) { }); }); + /** + * Enables/disables the state of "Auto Update In Progress" in UpdateHandler.json + */ + function setAutoUpdateInProgressFlag(flag) { + updateJsonHandler.parse() + .done(function() { + setUpdateStateInJSON("autoUpdateInProgress", flag); + }); + } + /** * Enables/disables the state of "Check For Updates" menu entry under Help Menu @@ -554,6 +636,8 @@ define(function (require, exports, module) { enableCheckForUpdateEntry(true); console.error(message); + + setUpdateStateInJSON("autoUpdateInProgress", false); } /** @@ -611,8 +695,18 @@ define(function (require, exports, module) { --downloadAttemptsRemaining; }; - setUpdateStateInJSON('downloadCompleted', false) - .done(downloadFn); + + if(!isAutoUpdateInitiated) { + isAutoUpdateInitiated = true; + updateJsonHandler.refresh() + .done(function() { + setUpdateStateInJSON('downloadCompleted', false) + .done(downloadFn); + }); + } else { + setUpdateStateInJSON('downloadCompleted', false) + .done(downloadFn); + } } /** @@ -644,6 +738,8 @@ define(function (require, exports, module) { title: Strings.DOWNLOAD_FAILED, description: Strings.INTERNET_UNAVAILABLE }); + + setUpdateStateInJSON("autoUpdateInProgress", false); } } @@ -867,6 +963,8 @@ define(function (require, exports, module) { description: Strings.CLICK_RESTART_TO_UPDATE, needButtons: true }); + + setUpdateStateInJSON("autoUpdateInProgress", false); HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOADCOMPLETE_UPDATE_BAR_RENDERED, "autoUpdate", @@ -876,8 +974,17 @@ define(function (require, exports, module) { ); }; - setUpdateStateInJSON('downloadCompleted', true) - .done(statusValidFn); + if(!isAutoUpdateInitiated) { + isAutoUpdateInitiated = true; + updateJsonHandler.refresh() + .done(function() { + setUpdateStateInJSON('downloadCompleted', true) + .done(statusValidFn); + }); + } else { + setUpdateStateInJSON('downloadCompleted', true) + .done(statusValidFn); + } } else { // Installer validation failed @@ -922,6 +1029,8 @@ define(function (require, exports, module) { title: Strings.VALIDATION_FAILED, description: descriptionMessage }); + + setUpdateStateInJSON("autoUpdateInProgress", false); } } } @@ -965,6 +1074,8 @@ define(function (require, exports, module) { title: Strings.DOWNLOAD_FAILED, description: descriptionMessage }); + + setUpdateStateInJSON("autoUpdateInProgress", false); } } @@ -995,6 +1106,10 @@ define(function (require, exports, module) { } + function _handleAppClose() { + postMessageToNode(MessageIds.REMOVE_FROM_REQUESTERS); + } + /** * Generates a map for brackets side functions */ @@ -1007,8 +1122,10 @@ define(function (require, exports, module) { functionMap["brackets.notifySafeToDownload"] = handleSafeToDownload; functionMap["brackets.notifyvalidationStatus"] = handleValidationStatus; functionMap["brackets.notifyInstallationStatus"] = handleInstallationStatus; - } + ProjectManager.on("beforeProjectClose beforeAppClose", _handleAppClose); + } + functionMap["brackets.setAutoUpdateInProgress"] = setAutoUpdateInProgressFlag; functionMap["brackets.registerBracketsFunctions"] = registerBracketsFunctions; }); diff --git a/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js index 0556c4848c5..9e635d8f1b1 100644 --- a/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js +++ b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js @@ -55,7 +55,7 @@ // function map for node functions var functionMap = {}; - var _nodeErrorMessages = { + var nodeErrorMessages = { UPDATEDIR_READ_FAILED: 0, UPDATEDIR_CLEAN_FAILED: 1, CHECKSUM_DID_NOT_MATCH: 2, @@ -64,17 +64,20 @@ NETWORK_SLOW_OR_DISCONNECTED: 5 }; + var requesters = {}, + isNodeDomainInitialized = false; + /** * Gets the arguments to a function in an array * @param {object} args - the arguments object * @returns {Array} - array of actual arguments */ function getFunctionArgs(args) { - if (args.length > 1) { - var fnArgs = new Array(args.length - 1), + if (args.length > 2) { + var fnArgs = new Array(args.length - 2), i; - for (i = 1; i < args.length; ++i) { - fnArgs[i - 1] = args[i]; + for (i = 2; i < args.length; ++i) { + fnArgs[i - 2] = args[i]; } return fnArgs; } @@ -86,13 +89,20 @@ * Posts messages to brackets * @param {string} messageId - Message to be passed */ - function postMessageToBrackets(messageId) { + function postMessageToBrackets(messageId, requester) { + if(!requesters[requester]) { + for (var key in requesters) { + requester = key; + break; + } + } + var msgObj = { fn: messageId, - args: getFunctionArgs(arguments) + args: getFunctionArgs(arguments), + requester: requester.toString() }; _domainManager.emitEvent('AutoUpdate', 'data', [msgObj]); - } /** @@ -115,13 +125,14 @@ * filePath - path to the file, * expectedChecksum - the checksum to validate against } */ - function validateChecksum(params) { + function validateChecksum(requester, params) { params = params || { filePath: installerPath, expectedChecksum: _updateParams.checksum }; - var hash = crypto.createHash('sha256'); + var hash = crypto.createHash('sha256'), + currentRequester = requester || ""; if (fs.existsSync(params.filePath)) { var stream = fs.createReadStream(params.filePath); @@ -154,17 +165,17 @@ } else { status = { valid: false, - err: _nodeErrorMessages.CHECKSUM_DID_NOT_MATCH + err: nodeErrorMessages.CHECKSUM_DID_NOT_MATCH }; } - postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, status); + postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, currentRequester, status); }); } else { var status = { valid: false, - err: _nodeErrorMessages.INSTALLER_NOT_FOUND + err: nodeErrorMessages.INSTALLER_NOT_FOUND }; - postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, status); + postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, currentRequester, status); } } @@ -210,17 +221,18 @@ * @param{Object} searchParams is object contains Information Error String * Encoding of Log File Update Diectory Path. */ - function checkInstallerStatus(searchParams) { + function checkInstallerStatus(requester, searchParams) { var installErrorStr = searchParams.installErrorStr, bracketsErrorStr = searchParams.bracketsErrorStr, updateDirectory = searchParams.updateDir, encoding = searchParams.encoding || "utf8", statusObj = {installError: ": BA_UN"}, - logFileAvailable = false; + logFileAvailable = false, + currentRequester = requester || ""; var notifyBrackets = function notifyBrackets(errorline) { statusObj.installError = errorline || ": BA_UN"; - postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, statusObj); + postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, currentRequester, statusObj); }; var parseLog = function (files) { @@ -239,7 +251,7 @@ } }); if (!logFileAvailable) { - postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, statusObj); + postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, currentRequester, statusObj); } }; @@ -247,7 +259,7 @@ .then(function (files) { return parseLog(files); }).catch(function () { - postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, statusObj); + postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, currentRequester, statusObj); }); } @@ -257,8 +269,9 @@ * sent back to Brackets, false otherwise * @param {object} [updateParams=_updateParams] - json containing update parameters */ - function downloadInstaller(isInitialAttempt, updateParams) { + function downloadInstaller(requester, isInitialAttempt, updateParams) { updateParams = updateParams || _updateParams; + var currentRequester = requester || ""; try { var ext = path.extname(updateParams.installerName); var localInstallerPath = path.resolve(updateDir, Date.now().toString() + ext), @@ -279,33 +292,34 @@ val: info }] }; - postMessageToBrackets(MessageIds.SHOW_STATUS_INFO, status); + postMessageToBrackets(MessageIds.SHOW_STATUS_INFO, currentRequester, status); }) .on('error', function (err) { console.log("AutoUpdate : Download failed. Error occurred : " + err.toString()); requestCompleted = false; localInstallerFile.end(); var error = err.code === 'ESOCKETTIMEDOUT' || err.code === 'ENOTFOUND' ? - _nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED : - _nodeErrorMessages.DOWNLOAD_ERROR; - postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, error); + nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED : + nodeErrorMessages.DOWNLOAD_ERROR; + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, currentRequester, error); }) .pipe(localInstallerFile) .on('close', function () { if (requestCompleted) { try { fs.renameSync(localInstallerPath, installerPath); - postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_SUCCESS); + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_SUCCESS, currentRequester); } catch (e) { console.log("AutoUpdate : Download failed. Exception occurred : " + e.toString()); postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, - _nodeErrorMessages.DOWNLOAD_ERROR); + currentRequester, nodeErrorMessages.DOWNLOAD_ERROR); } } }); } catch (e) { console.log("AutoUpdate : Download failed. Exception occurred : " + e.toString()); - postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, _nodeErrorMessages.DOWNLOAD_ERROR); + postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, + currentRequester, nodeErrorMessages.DOWNLOAD_ERROR); } } @@ -315,8 +329,8 @@ * @param {boolean} notifyBack - true if Brackets needs to be * notified post cleanup, false otherwise */ - function performCleanup(filesToCache, notifyBack) { - + function performCleanup(requester, filesToCache, notifyBack) { + var currentRequester = requester || ""; function filterFilesAndNotify(files, filesToCacheArr, notifyBackToBrackets) { files.forEach(function (file) { var fileExt = path.extname(path.basename(file)); @@ -330,7 +344,7 @@ } }); if (notifyBackToBrackets) { - postMessageToBrackets(MessageIds.NOTIFY_SAFE_TO_DOWNLOAD); + postMessageToBrackets(MessageIds.NOTIFY_SAFE_TO_DOWNLOAD, currentRequester); } } @@ -345,7 +359,7 @@ .catch(function (err) { console.log("AutoUpdate : Error in Reading Update Dir for Cleanup : " + err.toString()); postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, - _nodeErrorMessages.UPDATEDIR_READ_FAILED); + currentRequester, nodeErrorMessages.UPDATEDIR_READ_FAILED); }); } else { fs.remove(updateDir) @@ -355,7 +369,7 @@ .catch(function (err) { console.log("AutoUpdate : Error in Cleaning Update Dir : " + err.toString()); postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, - _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); + currentRequester, nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); }); } } @@ -363,7 +377,7 @@ .catch(function (err) { console.log("AutoUpdate : Error in Reading Update Dir stats for Cleanup : " + err.toString()); postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, - _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); + currentRequester, nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); }); } @@ -371,13 +385,20 @@ * Initializes the node with update parameters * @param {object} updateParams - json containing update parameters */ - function initializeState(updateParams) { + function initializeState(requester, updateParams) { + var currentRequester = requester || ""; _updateParams = updateParams; installerPath = path.resolve(updateDir, updateParams.installerName); - postMessageToBrackets(MessageIds.NOTIFY_INITIALIZATION_COMPLETE); + postMessageToBrackets(MessageIds.NOTIFY_INITIALIZATION_COMPLETE, currentRequester); } + function removeFromRequesters(requester) { + if (requesters.hasOwnProperty(requester.toString())) { + delete requesters[requester]; + } + } + /** * Generates a map for node side functions */ @@ -387,21 +408,28 @@ functionMap["node.validateInstaller"] = validateChecksum; functionMap["node.initializeState"] = initializeState; functionMap["node.checkInstallerStatus"] = checkInstallerStatus; + functionMap["node.removeFromRequesters"] = removeFromRequesters; } /** * Initializes node for the auto update, registers messages and node side funtions * @param {object} initObj - json containing init information { * messageIds : Messages for brackets and node communication - * updateDir : update directory in Appdata } + * updateDir : update directory in Appdata + * requester : ID of the current requester domain} */ function initNode(initObj) { - MessageIds = initObj.messageIds; - updateDir = path.resolve(initObj.updateDir); - logFilePath = path.resolve(updateDir, logFile); - installStatusFilePath = path.resolve(updateDir, installStatusFile); - registerNodeFunctions(); - postMessageToBrackets(MessageIds.REGISTER_BRACKETS_FUNCTIONS); + if (!isNodeDomainInitialized) { + MessageIds = initObj.messageIds; + updateDir = path.resolve(initObj.updateDir); + logFilePath = path.resolve(updateDir, logFile); + installStatusFilePath = path.resolve(updateDir, installStatusFile); + registerNodeFunctions(); + isNodeDomainInitialized = true; + postMessageToBrackets(MessageIds.SET_UPDATE_IN_PROGRESS_STATE, initObj.requester.toString(), false); + } + requesters[initObj.requester.toString()] = true; + postMessageToBrackets(MessageIds.REGISTER_BRACKETS_FUNCTIONS, initObj.requester.toString()); } @@ -412,7 +440,9 @@ * args - arguments to the above function } */ function receiveMessageFromBrackets(msgObj) { - functionMap[msgObj.fn].apply(null, msgObj.args); + var argList = msgObj.args; + argList.unshift(msgObj.requester || ""); + functionMap[msgObj.fn].apply(null, argList); } /** diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 85622c529d4..da41461aadf 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -865,10 +865,12 @@ define({ "CHECKSUM_DID_NOT_MATCH" : "Checksum didn't match.", "INSTALLER_NOT_FOUND" : "Installer not found.", "DOWNLOAD_ERROR" : "Error occurred while downloading.", - "NETWORK_SLOW_OR_DISCONNECTED" : "Network is Disconnected or too slow.", + "NETWORK_SLOW_OR_DISCONNECTED" : "Network is disconnected or too slow.", "RESTART_BUTTON" : "Restart", "LATER_BUTTON" : "Later", "DESCRIPTION_AUTO_UPDATE" : "Enable/disable Brackets Auto-update", + "AUTOUPDATE_ERROR" : "Error!", + "AUTOUPDATE_IN_PROGRESS" : "An update is already in progress.", // Strings for Related Files "CMD_FIND_RELATED_FILES" : "Find Related Files" From 288449bc3a8ee9e665d9120aaa19fce9e2e4b687 Mon Sep 17 00:00:00 2001 From: Vickramaditya Dhawal Date: Mon, 28 May 2018 16:05:36 +0530 Subject: [PATCH 036/149] Adding a pre release build step (#14360) * adding a new grunt task to generate pre-release builds with the proper auto update url * fixing eslint issues * further changes to pre release builds * fixing the access of config obj --- Gruntfile.js | 16 +++++++++++++--- src/brackets.config.dev.json | 3 ++- src/brackets.config.dist.json | 3 ++- src/brackets.config.json | 1 - src/brackets.config.prerelease.json | 7 +++++++ src/config.json | 6 +++--- src/nls/root/strings.js | 1 + src/strings.js | 6 +++++- tasks/write-config.js | 2 ++ 9 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 src/brackets.config.prerelease.json diff --git a/Gruntfile.js b/Gruntfile.js index 78c3b4f6986..fe7e2d0a1d0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -393,9 +393,7 @@ module.exports = function (grunt) { // Update version number in package.json and rewrite src/config.json grunt.registerTask('set-release', ['update-release-number', 'write-config:dev']); - // task: build - grunt.registerTask('build', [ - 'write-config:dist', + grunt.registerTask('build-common', [ 'eslint:src', 'jasmine', 'clean', @@ -414,6 +412,18 @@ module.exports = function (grunt) { 'build-config' ]); + // task: build + grunt.registerTask('build', [ + 'write-config:dist', + 'build-common' + ]); + + // task: build + grunt.registerTask('build-prerelease', [ + 'write-config:prerelease', + 'build-common' + ]); + // Default task. grunt.registerTask('default', ['test']); }; diff --git a/src/brackets.config.dev.json b/src/brackets.config.dev.json index 8f567b28d21..efe16154593 100644 --- a/src/brackets.config.dev.json +++ b/src/brackets.config.dev.json @@ -2,5 +2,6 @@ "healthDataServerURL" : "https://healthdev.brackets.io/healthDataLog", "analyticsDataServerURL" : "https://cc-api-data-stage.adobe.io/ingest", "serviceKey" : "brackets-service", - "environment" : "stage" + "environment" : "stage", + "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json" } diff --git a/src/brackets.config.dist.json b/src/brackets.config.dist.json index e84b7671072..df7e8b28384 100644 --- a/src/brackets.config.dist.json +++ b/src/brackets.config.dist.json @@ -2,5 +2,6 @@ "healthDataServerURL" : "https://health.brackets.io/healthDataLog", "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", "serviceKey" : "brackets-service", - "environment" : "production" + "environment" : "production", + "update_info_url" : "https://getupdates.brackets.io/getupdates/" } diff --git a/src/brackets.config.json b/src/brackets.config.json index 89a0db49067..12f8d25b1aa 100644 --- a/src/brackets.config.json +++ b/src/brackets.config.json @@ -4,7 +4,6 @@ "app_title" : "Brackets", "app_name_about" : "Brackets", "about_icon" : "styles/images/brackets_icon.svg", - "update_info_url" : "https://getupdates.brackets.io/getupdates/", "how_to_use_url" : "https://github.com/adobe/brackets/wiki/How-to-Use-Brackets", "support_url" : "https://github.com/adobe/brackets/wiki/Troubleshooting", "suggest_feature_url" : "https://github.com/adobe/brackets/wiki/Suggest-a-Feature", diff --git a/src/brackets.config.prerelease.json b/src/brackets.config.prerelease.json new file mode 100644 index 00000000000..08d76fc1dda --- /dev/null +++ b/src/brackets.config.prerelease.json @@ -0,0 +1,7 @@ +{ + "healthDataServerURL" : "https://health.brackets.io/healthDataLog", + "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", + "serviceKey" : "brackets-service", + "environment" : "prerelease", + "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json" +} diff --git a/src/config.json b/src/config.json index 4158ed87d04..26d7b240ddb 100644 --- a/src/config.json +++ b/src/config.json @@ -3,7 +3,6 @@ "app_title": "Brackets", "app_name_about": "Brackets", "about_icon": "styles/images/brackets_icon.svg", - "update_info_url": "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json", "how_to_use_url": "https://github.com/adobe/brackets/wiki/How-to-Use-Brackets", "support_url": "https://github.com/adobe/brackets/wiki/Troubleshooting", "suggest_feature_url": "https://github.com/adobe/brackets/wiki/Suggest-a-Feature", @@ -23,7 +22,8 @@ "healthDataServerURL": "https://healthdev.brackets.io/healthDataLog", "analyticsDataServerURL": "https://cc-api-data-stage.adobe.io/ingest", "serviceKey": "brackets-service", - "environment": "stage" + "environment": "stage", + "update_info_url": "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json" }, "name": "Brackets", "version": "1.13.0-0", @@ -99,4 +99,4 @@ "url": "https://github.com/adobe/brackets/blob/master/LICENSE" } ] -} +} \ No newline at end of file diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index da41461aadf..1e856955636 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -457,6 +457,7 @@ define({ "EXPERIMENTAL_BUILD" : "experimental build", "RELEASE_BUILD" : "build", "DEVELOPMENT_BUILD" : "development build", + "PRERELEASE_BUILD" : "prerelease build", "RELOAD_FROM_DISK" : "Reload from Disk", "KEEP_CHANGES_IN_EDITOR" : "Keep Changes in Editor", "CLOSE_DONT_SAVE" : "Close (Don't Save)", diff --git a/src/strings.js b/src/strings.js index f7da33aba47..57c1b910ed3 100644 --- a/src/strings.js +++ b/src/strings.js @@ -56,7 +56,11 @@ define(function (require, exports, module) { if (isDevBuild) { additionalGlobals.BUILD_TYPE = strings.DEVELOPMENT_BUILD; } else { - additionalGlobals.BUILD_TYPE = strings.RELEASE_BUILD; + if (brackets.config.environment === 'production') { + additionalGlobals.BUILD_TYPE = strings.RELEASE_BUILD; + } else { + additionalGlobals.BUILD_TYPE = strings.PRERELEASE_BUILD; + } } // Insert application strings diff --git a/tasks/write-config.js b/tasks/write-config.js index 46f54c400eb..d87b1fbce91 100644 --- a/tasks/write-config.js +++ b/tasks/write-config.js @@ -34,6 +34,8 @@ module.exports = function (grunt) { var name = "dev"; if (this.flags.dist === true) { name = "dist"; + } else if (this.flags.prerelease === true) { + name = "prerelease"; } var appConfigJSON = grunt.file.readJSON("src/brackets.config.json"), From 04c4bb33e2ff317ba4f178c88218ab3f331f0d4e Mon Sep 17 00:00:00 2001 From: Vickramaditya Dhawal Date: Tue, 29 May 2018 17:05:55 +0530 Subject: [PATCH 037/149] Introducing a buildtype to config (#14369) * adding a buildtype as prerelease and switching back the environemnt to production as the analytics server expects that * switching the environment actually to production --- src/brackets.config.dev.json | 3 ++- src/brackets.config.dist.json | 3 ++- src/brackets.config.prerelease.json | 5 +++-- src/strings.js | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/brackets.config.dev.json b/src/brackets.config.dev.json index efe16154593..bebcd37a9a2 100644 --- a/src/brackets.config.dev.json +++ b/src/brackets.config.dev.json @@ -3,5 +3,6 @@ "analyticsDataServerURL" : "https://cc-api-data-stage.adobe.io/ingest", "serviceKey" : "brackets-service", "environment" : "stage", - "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json" + "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json", + "buildtype" : "dev" } diff --git a/src/brackets.config.dist.json b/src/brackets.config.dist.json index df7e8b28384..9fdc775054b 100644 --- a/src/brackets.config.dist.json +++ b/src/brackets.config.dist.json @@ -3,5 +3,6 @@ "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", "serviceKey" : "brackets-service", "environment" : "production", - "update_info_url" : "https://getupdates.brackets.io/getupdates/" + "update_info_url" : "https://getupdates.brackets.io/getupdates/", + "buildtype" : "production" } diff --git a/src/brackets.config.prerelease.json b/src/brackets.config.prerelease.json index 08d76fc1dda..ee527136d4b 100644 --- a/src/brackets.config.prerelease.json +++ b/src/brackets.config.prerelease.json @@ -2,6 +2,7 @@ "healthDataServerURL" : "https://health.brackets.io/healthDataLog", "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", "serviceKey" : "brackets-service", - "environment" : "prerelease", - "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json" + "environment" : "production", + "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json", + "buildtype" : "prerelease" } diff --git a/src/strings.js b/src/strings.js index 57c1b910ed3..2adeea6f291 100644 --- a/src/strings.js +++ b/src/strings.js @@ -56,7 +56,7 @@ define(function (require, exports, module) { if (isDevBuild) { additionalGlobals.BUILD_TYPE = strings.DEVELOPMENT_BUILD; } else { - if (brackets.config.environment === 'production') { + if (brackets.config.buildtype === 'production') { additionalGlobals.BUILD_TYPE = strings.RELEASE_BUILD; } else { additionalGlobals.BUILD_TYPE = strings.PRERELEASE_BUILD; From e5018f74f65154fd99ba588c8a559506e7116065 Mon Sep 17 00:00:00 2001 From: niteskum Date: Fri, 1 Jun 2018 12:50:41 +0530 Subject: [PATCH 038/149] Fix for Bug #14379 --- src/extensions/default/AutoUpdate/UpdateStatus.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/extensions/default/AutoUpdate/UpdateStatus.js b/src/extensions/default/AutoUpdate/UpdateStatus.js index 5508add4bd4..9eac341ae48 100644 --- a/src/extensions/default/AutoUpdate/UpdateStatus.js +++ b/src/extensions/default/AutoUpdate/UpdateStatus.js @@ -48,8 +48,10 @@ define(function (require, exports, module) { var $updateStatus = $(Mustache.render(UpdateStatusHtml, {"Strings": Strings})); $updateStatus.appendTo('#status-bar'); - var valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, 0); - $('#update-status #' + id + ' #' + 'percent').text(valStr); + if(id === "initial-download") { + var valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, 0); + $('#update-status #' + id + ' #' + 'percent').text(valStr); + } $('#update-status #' + id).show(); } @@ -64,7 +66,10 @@ define(function (require, exports, module) { */ function modifyUpdateStatus(statusObj) { statusObj.spans.forEach(function (span) { - var valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, span.val.split('%')[0]); + var valStr = span.val; + if(span.id === "percent") { + valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, span.val.split('%')[0]); + } $('#update-status #' + statusObj.target + ' #' + span.id).text(valStr); }); } From db9583f4bfa2412b7dacb5f1219ef151b823a4a4 Mon Sep 17 00:00:00 2001 From: walfgithub Date: Mon, 4 Jun 2018 14:58:57 +0530 Subject: [PATCH 039/149] ALF Automation (#14333) Merging strings for 1.13 release. --- src/nls/fr/strings.js | 5 +++++ src/nls/ja/strings.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 42397b298f8..bb199373bcc 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -457,6 +457,7 @@ define({ "EXPERIMENTAL_BUILD": "version expérimentale", "RELEASE_BUILD": "édition", "DEVELOPMENT_BUILD": "version de développement", + "PRERELEASE_BUILD": "version préliminaire", "RELOAD_FROM_DISK": "Recharger à partir du disque", "KEEP_CHANGES_IN_EDITOR": "Conserver les modifications dans l’éditeur", "CLOSE_DONT_SAVE": "Fermer (sans enregistrer)", @@ -865,10 +866,14 @@ define({ "CHECKSUM_DID_NOT_MATCH": "La somme de contrôle ne correspond pas.", "INSTALLER_NOT_FOUND": "Programme d’installation non trouvé.", "DOWNLOAD_ERROR": "Erreur lors du téléchargement.", + "NETWORK_SLOW_OR_DISCONNECTED": "Le réseau est déconnecté ou trop lent.", "RESTART_BUTTON": "Recommencer", "LATER_BUTTON": "Plus tard", "DESCRIPTION_AUTO_UPDATE": "Activer/désactiver la mise à jour automatique de Brackets", + "AUTOUPDATE_ERROR": "Erreur !", + "AUTOUPDATE_IN_PROGRESS": "Une mise à jour est déjà en cours.", + "NUMBER_WITH_PERCENTAGE": "{0}%", // Strings for Related Files "CMD_FIND_RELATED_FILES": "Trouver les fichiers associés" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 48ca3dafcf6..85428d85fcb 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -457,6 +457,7 @@ define({ "EXPERIMENTAL_BUILD": "試験ビルド", "RELEASE_BUILD": "ビルド", "DEVELOPMENT_BUILD": "開発ビルド", + "PRERELEASE_BUILD": "プレリリースビルド", "RELOAD_FROM_DISK": "ディスクから再読み込み", "KEEP_CHANGES_IN_EDITOR": "エディター内の変更を保持する", "CLOSE_DONT_SAVE": "保存せずに閉じる", @@ -865,10 +866,14 @@ define({ "CHECKSUM_DID_NOT_MATCH": "チェックサムが一致しませんでした。", "INSTALLER_NOT_FOUND": "インストーラーが見つかりません。", "DOWNLOAD_ERROR": "ダウンロード中にエラーが発生しました。", + "NETWORK_SLOW_OR_DISCONNECTED": "ネットワーク接続がないか速度が遅すぎます。", "RESTART_BUTTON": "再起動", "LATER_BUTTON": "後で再起動", "DESCRIPTION_AUTO_UPDATE": "Brackets の自動更新を有効化/無効化", + "AUTOUPDATE_ERROR": "エラー!", + "AUTOUPDATE_IN_PROGRESS": "更新は既に進行中です。", + "NUMBER_WITH_PERCENTAGE": "{0}%", // Strings for Related Files "CMD_FIND_RELATED_FILES": "関連するファイルを検索" }); From defded0cafa7a7e815ba30d5c8babaa483042dde Mon Sep 17 00:00:00 2001 From: Sorab Bisht Date: Tue, 12 Jun 2018 23:04:42 -0700 Subject: [PATCH 040/149] Fix for FindInFiles UI not opening when no file is open (#14416) * Add null check for editor * Cleanup: Use tertiary operator --- src/search/FindBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search/FindBar.js b/src/search/FindBar.js index d15cdd7fe93..e655b6b763e 100644 --- a/src/search/FindBar.js +++ b/src/search/FindBar.js @@ -705,7 +705,7 @@ define(function (require, exports, module) { */ FindBar.getInitialQuery = function (currentFindBar, editor) { var query, - selection = FindBar._getInitialQueryFromSelection(editor), + selection = editor ? FindBar._getInitialQueryFromSelection(editor) : "", replaceText = ""; if (currentFindBar && !currentFindBar.isClosed()) { From 10b651b18ac766dc5ae992f76a0e4b28d37c6a28 Mon Sep 17 00:00:00 2001 From: Sorab Bisht Date: Wed, 13 Jun 2018 22:51:15 -0700 Subject: [PATCH 041/149] Fix for Title bar text and Save button issue for empty active pane (#14421) * Adding null checks to fix title change issue * Change the menu items visibility when file is present in active pane * Enable Menu options when no file is present in active pane --- src/document/DocumentCommandHandlers.js | 4 ++-- src/extensions/default/RemoteFileAdapter/main.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index 618ada5adca..28ea9a9dfb0 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -158,8 +158,8 @@ define(function (require, exports, module) { var currentDoc = DocumentManager.getCurrentDocument(), windowTitle = brackets.config.app_title, currentlyViewedFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE), - currentlyViewedPath = currentlyViewedFile.fullPath, - readOnlyString = currentlyViewedFile.readOnly ? "[Read Only] - " : ""; + currentlyViewedPath = currentlyViewedFile && currentlyViewedFile.fullPath, + readOnlyString = (currentlyViewedFile && currentlyViewedFile.readOnly) ? "[Read Only] - " : ""; if (!brackets.nativeMenus) { if (currentlyViewedPath) { diff --git a/src/extensions/default/RemoteFileAdapter/main.js b/src/extensions/default/RemoteFileAdapter/main.js index 143e7ff3c75..5feca37ce8b 100644 --- a/src/extensions/default/RemoteFileAdapter/main.js +++ b/src/extensions/default/RemoteFileAdapter/main.js @@ -59,7 +59,8 @@ define(function (require, exports, module) { function _setMenuItemsVisible() { var file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE), cMenuItems = [Commands.FILE_SAVE, Commands.FILE_RENAME, Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS], - enable = (file.constructor.name !== "RemoteFile"); + // Enable menu options when no file is present in active pane + enable = !file || (file.constructor.name !== "RemoteFile"); // Enable or disable commands based on whether the file is a remoteFile or not. cMenuItems.forEach(function (item) { From 93cfeacca60b30ef7cfcfacbc434f2f4d3fda624 Mon Sep 17 00:00:00 2001 From: Sorab Bisht Date: Thu, 14 Jun 2018 22:55:08 -0700 Subject: [PATCH 042/149] Version bump to 1.14 (#14396) --- package.json | 4 ++-- src/config.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 67a939326f1..ddc92dd54af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Brackets", - "version": "1.13.0-0", - "apiVersion": "1.13.0", + "version": "1.14.0-0", + "apiVersion": "1.14.0", "homepage": "http://brackets.io", "issues": { "url": "http://github.com/adobe/brackets/issues" diff --git a/src/config.json b/src/config.json index 26d7b240ddb..ef4e55d96b2 100644 --- a/src/config.json +++ b/src/config.json @@ -26,8 +26,8 @@ "update_info_url": "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json" }, "name": "Brackets", - "version": "1.13.0-0", - "apiVersion": "1.13.0", + "version": "1.14.0-0", + "apiVersion": "1.14.0", "homepage": "http://brackets.io", "issues": { "url": "http://github.com/adobe/brackets/issues" From 7c29cc57f100bce146698c1d33f577f2aa09b197 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Tue, 24 Jul 2018 13:29:15 +0530 Subject: [PATCH 043/149] Auto Update Error Handlng Fix (#14412) * Auto Update Error Handlng Fix * Addressed Review comments --- .../default/AutoUpdate/MessageIds.js | 2 +- src/extensions/default/AutoUpdate/main.js | 39 ++++++++++--------- .../AutoUpdate/node/AutoUpdateDomain.js | 4 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/extensions/default/AutoUpdate/MessageIds.js b/src/extensions/default/AutoUpdate/MessageIds.js index b969aab6150..464ddf48836 100644 --- a/src/extensions/default/AutoUpdate/MessageIds.js +++ b/src/extensions/default/AutoUpdate/MessageIds.js @@ -38,6 +38,6 @@ define(function (require, exports, module) { exports.NOTIFY_INITIALIZATION_COMPLETE = "brackets.notifyinitializationComplete"; exports.NOTIFY_VALIDATION_STATUS = "brackets.notifyvalidationStatus"; exports.NOTIFY_INSTALLATION_STATUS = "brackets.notifyInstallationStatus"; - exports.SET_UPDATE_IN_PROGRESS_STATE = "brackets.setAutoUpdateInProgress"; + exports.NODE_DOMAIN_INITIALIZED = "brackets.nodeDomainInitialized"; exports.REGISTER_BRACKETS_FUNCTIONS = "brackets.registerBracketsFunctions"; }); diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index 1a36c486178..da8eff67c4b 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -223,6 +223,7 @@ define(function (require, exports, module) { if (downloadCompleted && updateInitiatedInPrevSession) { var isNewVersion = checkIfVersionUpdated(); + updateJsonHandler.reset(); if (isNewVersion) { // We get here if the update was successful UpdateInfoBar.showUpdateBar({ @@ -280,19 +281,14 @@ define(function (require, exports, module) { /** * Initializes the state of parsed content from updateHelper.json + * returns Promise Object Which is resolved when parsing is success + * and rejected if parsing is failed. */ function initState() { + var result = $.Deferred(); updateJsonHandler.parse() .done(function() { - checkIfAnotherSessionInProgress() - .done(function (inProgress) { - if (!inProgress) { - checkUpdateStatus(); - } - }) - .fail(function () { - checkUpdateStatus(); - }); + result.resolve(); }) .fail(function (code) { var logMsg; @@ -311,7 +307,9 @@ define(function (require, exports, module) { break; } console.log(logMsg); + result.reject(); }); + return result.promise(); } @@ -321,15 +319,13 @@ define(function (require, exports, module) { */ function setupAutoUpdate() { updateJsonHandler = new StateHandler(updateJsonPath); + updateDomain.on('data', receiveMessageFromNode); updateDomain.exec('initNode', { messageIds: MessageIds, updateDir: updateDir, requester: domainID }); - - updateDomain.on('data', receiveMessageFromNode); - initState(); } @@ -594,11 +590,17 @@ define(function (require, exports, module) { /** * Enables/disables the state of "Auto Update In Progress" in UpdateHandler.json */ - function setAutoUpdateInProgressFlag(flag) { - updateJsonHandler.parse() - .done(function() { - setUpdateStateInJSON("autoUpdateInProgress", flag); - }); + function nodeDomainInitialized(reset) { + initState() + .done(function () { + var inProgress = updateJsonHandler.get(updateProgressKey); + if (inProgress && reset) { + setUpdateStateInJSON(updateProgressKey, !reset) + .always(checkUpdateStatus); + } else if (!inProgress) { + checkUpdateStatus(); + } + }); } @@ -636,7 +638,6 @@ define(function (require, exports, module) { enableCheckForUpdateEntry(true); console.error(message); - setUpdateStateInJSON("autoUpdateInProgress", false); } /** @@ -1124,7 +1125,7 @@ define(function (require, exports, module) { ProjectManager.on("beforeProjectClose beforeAppClose", _handleAppClose); } - functionMap["brackets.setAutoUpdateInProgress"] = setAutoUpdateInProgressFlag; + functionMap["brackets.nodeDomainInitialized"] = nodeDomainInitialized; functionMap["brackets.registerBracketsFunctions"] = registerBracketsFunctions; }); diff --git a/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js index 9e635d8f1b1..2f63ee3d7e7 100644 --- a/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js +++ b/src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js @@ -419,6 +419,7 @@ * requester : ID of the current requester domain} */ function initNode(initObj) { + var resetUpdateProgres = false; if (!isNodeDomainInitialized) { MessageIds = initObj.messageIds; updateDir = path.resolve(initObj.updateDir); @@ -426,8 +427,9 @@ installStatusFilePath = path.resolve(updateDir, installStatusFile); registerNodeFunctions(); isNodeDomainInitialized = true; - postMessageToBrackets(MessageIds.SET_UPDATE_IN_PROGRESS_STATE, initObj.requester.toString(), false); + resetUpdateProgres = true; } + postMessageToBrackets(MessageIds.NODE_DOMAIN_INITIALIZED, initObj.requester.toString(), resetUpdateProgres); requesters[initObj.requester.toString()] = true; postMessageToBrackets(MessageIds.REGISTER_BRACKETS_FUNCTIONS, initObj.requester.toString()); } From afcbff03bb51b1e275dac2f9f0afbfa23ef79856 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Thu, 2 Aug 2018 15:20:15 +0530 Subject: [PATCH 044/149] JSRefactoring Bugs Fix (#14455) * JSRefactoring Bugs Fix * Added Fix For TRY-CATCH Wraaper * Corrected the typo * Addressed Review Comments * Addressed Review Comments * Addressed Review Comments --- .../ExtractToVariable.js | 68 +++++++++++++++---- .../JavaScriptRefactoring/RefactoringUtils.js | 9 ++- .../JavaScriptRefactoring/RenameIdentifier.js | 20 ++++-- .../JavaScriptRefactoring/WrapSelection.js | 28 +++++--- src/nls/root/strings.js | 2 +- 5 files changed, 97 insertions(+), 30 deletions(-) diff --git a/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js b/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js index 554041900aa..926d8f516a5 100644 --- a/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js +++ b/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js @@ -37,19 +37,24 @@ define(function(require, exports, module) { * Does the actual extraction. i.e Replacing the text, Creating a variable * and multi select variable names */ - function extract(scopes, parentStatement, expns, text) { + function extract(scopes, parentStatement, expns, text, insertPosition) { var varType = "var", varName = RefactoringUtils.getUniqueIdentifierName(scopes, "extracted"), varDeclaration = varType + " " + varName + " = " + text + ";\n", - insertStartPos = session.editor.posFromIndex(parentStatement.start), + parentStatementStartPos = session.editor.posFromIndex(parentStatement.start), + insertStartPos = insertPosition || parentStatementStartPos, selections = [], doc = session.editor.document, replaceExpnIndex = 0, - posToIndent; + posToIndent, + edits = []; // If parent statement is expression statement, then just append var declaration // Ex: "add(1, 2)" will become "var extracted = add(1, 2)" - if (parentStatement.type === "ExpressionStatement" && RefactoringUtils.isEqual(parentStatement.expression, expns[0])) { + if (parentStatement.type === "ExpressionStatement" && + RefactoringUtils.isEqual(parentStatement.expression, expns[0]) && + insertStartPos.line === parentStatementStartPos.line && + insertStartPos.ch === parentStatementStartPos.ch) { varDeclaration = varType + " " + varName + " = "; replaceExpnIndex = 1; } @@ -63,9 +68,16 @@ define(function(require, exports, module) { expns[i].start = doc.adjustPosForChange(expns[i].start, varDeclaration.split("\n"), insertStartPos, insertStartPos); expns[i].end = doc.adjustPosForChange(expns[i].end, varDeclaration.split("\n"), insertStartPos, insertStartPos); - selections.push({ - start: expns[i].start, - end: {line: expns[i].start.line, ch: expns[i].start.ch + varName.length} + edits.push({ + edit: { + text: varName, + start: expns[i].start, + end: expns[i].end + }, + selection: { + start: expns[i].start, + end: {line: expns[i].start.line, ch: expns[i].start.ch + varName.length} + } }); } @@ -73,15 +85,12 @@ define(function(require, exports, module) { doc.batchOperation(function() { doc.replaceRange(varDeclaration, insertStartPos); - for (var i = replaceExpnIndex; i < expns.length; ++i) { - doc.replaceRange(varName, expns[i].start, expns[i].end); - } + selections = doc.doMultipleEdits(edits); selections.push({ start: {line: insertStartPos.line, ch: insertStartPos.ch + varType.length + 1}, end: {line: insertStartPos.line, ch: insertStartPos.ch + varType.length + varName.length + 1}, primary: true }); - session.editor.setSelections(selections); session.editor._codeMirror.indentLine(posToIndent.line, "smart"); }); @@ -163,6 +172,7 @@ define(function(require, exports, module) { */ function extractToVariable(ast, start, end, text, scopes) { var doc = session.editor.document, + editor = EditorManager.getActiveEditor(), parentExpn = RefactoringUtils.getExpression(ast, start, end, doc.getText()), expns = [], parentBlockStatement, @@ -178,8 +188,26 @@ define(function(require, exports, module) { if (doc.getText().substr(parentExpn.start, parentExpn.end - parentExpn.start) === text) { parentBlockStatement = RefactoringUtils.findSurroundASTNode(ast, parentExpn, ["BlockStatement", "Program"]); expns = findAllExpressions(parentBlockStatement, parentExpn, text); - parentStatement = RefactoringUtils.findSurroundASTNode(ast, expns[0], ["Statement"]); - extract(scopes, parentStatement, expns, text); + + RefactoringUtils.getScopeData(session, editor.posFromIndex(expns[0].start)).done(function(scope) { + var firstExpnsScopes = RefactoringUtils.getAllScopes(ast, scope, doc.getText()), + insertPostion; + parentStatement = RefactoringUtils.findSurroundASTNode(ast, expns[0], ["Statement"]); + if (scopes.length < firstExpnsScopes.length) { + var parentScope; + if (expns[0].body && expns[0].body.type === "BlockStatement") { + parentScope = firstExpnsScopes[firstExpnsScopes.length - scopes.length]; + } else { + parentScope = firstExpnsScopes[firstExpnsScopes.length - scopes.length - 1]; + } + + var insertNode = RefactoringUtils.findSurroundASTNode(ast, parentScope.originNode, ["Statement"]); + if (insertNode) { + insertPostion = session.editor.posFromIndex(insertNode.start); + } + } + extract(scopes, parentStatement, expns, text, insertPostion); + }); } else { parentStatement = RefactoringUtils.findSurroundASTNode(ast, parentExpn, ["Statement"]); extract(scopes, parentStatement, [{ start: start, end: end }], text); @@ -212,6 +240,16 @@ define(function(require, exports, module) { expns, inlineMenu; + function callExtractToVariable(startPos, endPos, value) { + RefactoringUtils.getScopeData(session, editor.posFromIndex(startPos)) + .done(function(expnscope) { + scopes = RefactoringUtils.getAllScopes(ast, expnscope, doc.getText()); + extractToVariable(ast, startPos, endPos, value, scopes); + }).fail(function() { + editor.displayErrorMessageAtCursor(Strings.ERROR_TERN_FAILED); + }); + } + RefactoringUtils.getScopeData(session, editor.posFromIndex(start)).done(function(scope) { ast = RefactoringUtils.getAST(doc.getText()); scopes = RefactoringUtils.getAllScopes(ast, scope, doc.getText()); @@ -253,7 +291,7 @@ define(function(require, exports, module) { // If only one surround expression, extract if (expns.length === 1) { - extractToVariable(ast, expns[0].start, expns[0].end, expns[0].value, scopes); + callExtractToVariable(expns[0].start, expns[0].end, expns[0].value); return; } @@ -271,7 +309,7 @@ define(function(require, exports, module) { inlineMenu.open(expns); inlineMenu.onSelect(function (expnId) { - extractToVariable(ast, expns[expnId].start, expns[expnId].end, expns[expnId].value, scopes); + callExtractToVariable(expns[expnId].start, expns[expnId].end, expns[expnId].value); inlineMenu.close(); }); diff --git a/src/extensions/default/JavaScriptRefactoring/RefactoringUtils.js b/src/extensions/default/JavaScriptRefactoring/RefactoringUtils.js index 76bc5b91b3a..c92e1f6f00a 100644 --- a/src/extensions/default/JavaScriptRefactoring/RefactoringUtils.js +++ b/src/extensions/default/JavaScriptRefactoring/RefactoringUtils.js @@ -467,7 +467,14 @@ define(function (require, exports, module) { * @return {Object} - Ast of current opened doc */ RefactoringSession.prototype.createAstOfCurrentDoc = function () { - return AcornLoose.parse_dammit(this.document.getText()); + var ast, + text = this.document.getText(); + try { + ast = Acorn.parse(text); + } catch(e) { + ast = Acorn.parse_dammit(text); + } + return ast; }; /** diff --git a/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js b/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js index 80e0d279e12..13448e9e6bb 100644 --- a/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js +++ b/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js @@ -127,13 +127,23 @@ define(function (require, exports, module) { } } - if (type === "local") { - editor.setSelections(refs); - } else { - editor.setSelections(refs.filter(function(element) { + var currentPosition = editor.posFromIndex(refsResp.offset), + refsArray = refs; + if (type !== "local") { + refsArray = refs.filter(function (element) { return isInSameFile(element, refsResp); - })); + }); } + + // Finding the Primary Reference in Array + var primaryRef = refsArray.find(function (element) { + return ((element.start.line === currentPosition.line || element.end.line === currentPosition.line) + && currentPosition.ch <= element.end.ch && currentPosition.ch >= element.start.ch); + }); + // Setting the primary flag of Primary Refence to true + primaryRef.primary = true; + + editor.setSelections(refsArray); } /** diff --git a/src/extensions/default/JavaScriptRefactoring/WrapSelection.js b/src/extensions/default/JavaScriptRefactoring/WrapSelection.js index 6d6dad468a2..78b89ae5bf7 100644 --- a/src/extensions/default/JavaScriptRefactoring/WrapSelection.js +++ b/src/extensions/default/JavaScriptRefactoring/WrapSelection.js @@ -97,7 +97,7 @@ define(function (require, exports, module) { }); if (wrapperName === TRY_CATCH) { - var cursorLine = current.editor.getSelection().start.line - 1, + var cursorLine = current.editor.getSelection().end.line - 1, startCursorCh = current.document.getLine(cursorLine).indexOf("\/\/"), endCursorCh = current.document.getLine(cursorLine).length; @@ -247,8 +247,9 @@ define(function (require, exports, module) { var token = TokenUtils.getTokenAt(current.cm, current.cm.posFromIndex(endIndex)), isLastNode, - lineEndPos, - templateParams; + templateParams, + parentNode, + propertyEndPos; //Create getters and setters only if selected reference is a property if (token.type !== "property") { @@ -256,15 +257,26 @@ define(function (require, exports, module) { return; } + parentNode = current.getParentNode(current.ast, endIndex); // Check if selected propery is child of a object expression - if (!current.getParentNode(current.ast, endIndex)) { + if (!parentNode || !parentNode.properties) { current.editor.displayErrorMessageAtCursor(Strings.ERROR_GETTERS_SETTERS); return; } + + var propertyNodeArray = parentNode.properties; + // Find the last Propery Node before endIndex + var properyEndNode = propertyNodeArray.find(function (element) { + return (endIndex >= element.start && endIndex < element.end); + }); + + //Get Current Selected Property End Index; + propertyEndPos = editor.posFromIndex(properyEndNode.end); + + //We have to add ',' so we need to find position of current property selected isLastNode = current.isLastNodeInScope(current.ast, endIndex); - lineEndPos = current.lineEndPosition(current.startPos.line); templateParams = { "getName": token.string, "setName": token.string, @@ -276,11 +288,11 @@ define(function (require, exports, module) { current.document.batchOperation(function() { if (isLastNode) { //Add ',' in the end of current line - current.document.replaceRange(",", lineEndPos, lineEndPos); - lineEndPos.ch++; + current.document.replaceRange(",", propertyEndPos, propertyEndPos); } + propertyEndPos.ch++; - current.editor.setSelection(lineEndPos); //Selection on line end + current.editor.setSelection(propertyEndPos); //Selection on line end // Add getters and setters for given token using template at current cursor position current.replaceTextFromTemplate(GETTERS_SETTERS, templateParams); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index a442bfdf9b9..594a566764e 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -689,7 +689,7 @@ define({ "ERROR_EXTRACTTO_VARIABLE_MULTICURSORS" : "Extract to Variable does not work in multicursors", "ERROR_EXTRACTTO_FUNCTION_MULTICURSORS" : "Extract to Function does not work in multicursors", "EXTRACTTO_FUNCTION_SELECT_SCOPE" : "Choose destination scope", - "EXTRACTTO_VARIABLE_SELECT_EXPRESSION" : "Select a expression", + "EXTRACTTO_VARIABLE_SELECT_EXPRESSION" : "Select an expression", "CMD_REFACTORING_RENAME" : "Rename", "CMD_REFACTORING_TRY_CATCH" : "Wrap in Try Catch", "CMD_REFACTORING_CONDITION" : "Wrap in Condition", From 0cee7d4575ab858ee029e95086719ac538f107ed Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Thu, 2 Aug 2018 18:54:10 +0530 Subject: [PATCH 045/149] Fixed ExtractToVariable Menu Close Issue due to scrolling (#14492) * Fixed ExtractToVariable Menu Close Issue due to scrolling * Addressed Review Comments * Addessed review comments * Addressed Review Comments * Addressed Review Comments --- .../JavaScriptRefactoring/ExtractToVariable.js | 13 +++++++++++++ src/widgets/InlineMenu.js | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js b/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js index 926d8f516a5..e5cb1b70412 100644 --- a/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js +++ b/src/extensions/default/JavaScriptRefactoring/ExtractToVariable.js @@ -303,6 +303,19 @@ define(function(require, exports, module) { inlineMenu = new InlineMenu(session.editor, Strings.EXTRACTTO_VARIABLE_SELECT_EXPRESSION); inlineMenu.onHover(function (expnId) { + // Remove the scroll Handlers If already Attached. + editor.off("scroll.inlinemenu"); + // Add a scroll handler If Selection Range is not View. + // This is Added for a Bug, where Menu used not to open for the first Time + if(!editor.isLineVisible(editor.posFromIndex(expns[expnId].end).line)) { + editor.on("scroll.inlinemenu", function() { + // Remove the Handlers so that If scroll event is triggerd again by any other operation + // Menu should not be reopened. + // Menu Should be reopened only if Scroll event is triggered by onHover. + editor.off("scroll.inlinemenu"); + inlineMenu.openRemovedMenu(); + }); + } editor.setSelection(editor.posFromIndex(expns[expnId].start), editor.posFromIndex(expns[expnId].end)); }); diff --git a/src/widgets/InlineMenu.js b/src/widgets/InlineMenu.js index 685b4bb3d13..30b34c6a2e5 100644 --- a/src/widgets/InlineMenu.js +++ b/src/widgets/InlineMenu.js @@ -413,6 +413,19 @@ define(function (require, exports, module) { } }; + /** + * Displays the last menu which was closed due to Scrolling + */ + InlineMenu.prototype.openRemovedMenu = function () { + if (this.opened === true) { + if (this.$menu && !this.$menu.hasClass("open")) { + var menuPos = this._calcMenuLocation(); + this.$menu.addClass("open") + .css({"left": menuPos.left, "top": menuPos.top, "width": menuPos.width + "px"}); + } + } + }; + /** * Closes the menu */ From fa00beb5f945caa133e706f9c0eff68c9edcecf1 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Fri, 10 Aug 2018 18:06:34 +0530 Subject: [PATCH 046/149] Fixed Getter Setter Redo Issue (#14508) * Fixed Getter Setter Redo Issue * Addressed Review Comments --- .../JavaScriptRefactoring/WrapSelection.js | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/extensions/default/JavaScriptRefactoring/WrapSelection.js b/src/extensions/default/JavaScriptRefactoring/WrapSelection.js index 78b89ae5bf7..7964b2a4cc9 100644 --- a/src/extensions/default/JavaScriptRefactoring/WrapSelection.js +++ b/src/extensions/default/JavaScriptRefactoring/WrapSelection.js @@ -246,6 +246,7 @@ define(function (require, exports, module) { } var token = TokenUtils.getTokenAt(current.cm, current.cm.posFromIndex(endIndex)), + commaString = ",", isLastNode, templateParams, parentNode, @@ -267,16 +268,38 @@ define(function (require, exports, module) { var propertyNodeArray = parentNode.properties; // Find the last Propery Node before endIndex - var properyEndNode = propertyNodeArray.find(function (element) { + var properyNodeIndex = propertyNodeArray.findIndex(function (element) { return (endIndex >= element.start && endIndex < element.end); }); + var propertyNode = propertyNodeArray[properyNodeIndex]; + //Get Current Selected Property End Index; - propertyEndPos = editor.posFromIndex(properyEndNode.end); + propertyEndPos = editor.posFromIndex(propertyNode.end); //We have to add ',' so we need to find position of current property selected isLastNode = current.isLastNodeInScope(current.ast, endIndex); + var nextPropertNode, nextPropertyStartPos; + if(!isLastNode && properyNodeIndex + 1 <= propertyNodeArray.length - 1) { + nextPropertNode = propertyNodeArray[properyNodeIndex + 1]; + nextPropertyStartPos = editor.posFromIndex(nextPropertNode.start); + + if(propertyEndPos.line !== nextPropertyStartPos.line) { + propertyEndPos = current.lineEndPosition(current.startPos.line); + } else { + propertyEndPos = nextPropertyStartPos; + commaString = ", "; + } + } + + var getSetPos; + if (isLastNode) { + getSetPos = current.document.adjustPosForChange(propertyEndPos, commaString.split("\n"), + propertyEndPos, propertyEndPos); + } else { + getSetPos = propertyEndPos; + } templateParams = { "getName": token.string, "setName": token.string, @@ -288,18 +311,17 @@ define(function (require, exports, module) { current.document.batchOperation(function() { if (isLastNode) { //Add ',' in the end of current line - current.document.replaceRange(",", propertyEndPos, propertyEndPos); + current.document.replaceRange(commaString, propertyEndPos, propertyEndPos); } - propertyEndPos.ch++; - current.editor.setSelection(propertyEndPos); //Selection on line end + current.editor.setSelection(getSetPos); //Selection on line end // Add getters and setters for given token using template at current cursor position current.replaceTextFromTemplate(GETTERS_SETTERS, templateParams); if (!isLastNode) { // Add ',' at the end setter - current.document.replaceRange(",", current.editor.getSelection().start, current.editor.getSelection().start); + current.document.replaceRange(commaString, current.editor.getSelection().start, current.editor.getSelection().start); } }); } From 8f5be981beaa9be55a457e17dbd906bfbfe31370 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Mon, 27 Aug 2018 19:08:11 +0530 Subject: [PATCH 047/149] jsRefactor Rename Relative Path issue fixed (#14520) * jsRefactor Rename Realtice Path issue fixed * Addressed review comments --- .../JavaScriptRefactoring/RenameIdentifier.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js b/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js index 13448e9e6bb..6fa53b5f48e 100644 --- a/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js +++ b/src/extensions/default/JavaScriptRefactoring/RenameIdentifier.js @@ -29,7 +29,8 @@ define(function (require, exports, module) { Session = brackets.getModule("JSUtils/Session"), MessageIds = brackets.getModule("JSUtils/MessageIds"), TokenUtils = brackets.getModule("utils/TokenUtils"), - Strings = brackets.getModule("strings"); + Strings = brackets.getModule("strings"), + ProjectManager = brackets.getModule("project/ProjectManager"); var session = null, // object that encapsulates the current session state keywords = ["define", "alert", "exports", "require", "module", "arguments"]; @@ -97,8 +98,22 @@ define(function (require, exports, module) { var result = new $.Deferred(); function isInSameFile(obj, refsResp) { + var projectRoot = ProjectManager.getProjectRoot(), + projectDir, + fileName = ""; + if (projectRoot) { + projectDir = projectRoot.fullPath; + } + + // get the relative path of File as Tern can also return + // references with file name as a relative path wrt projectRoot + // so refernce file name will be compared with both relative and absolute path to check if it is same file + if (projectDir && refsResp && refsResp.file && refsResp.file.indexOf(projectDir) === 0) { + fileName = refsResp.file.slice(projectDir.length); + } // In case of unsaved files, After renameing once Tern is returning filename without forward slash - return (obj && (obj.file === refsResp.file || obj.file === refsResp.file.slice(1, refsResp.file.length))); + return (obj && (obj.file === refsResp.file || obj.file === fileName + || obj.file === refsResp.file.slice(1, refsResp.file.length))); } /** From 3dce01bbdab8ce9f05407add9bebb34f6fcb7cb5 Mon Sep 17 00:00:00 2001 From: Alejandro Bar Acedo Date: Fri, 5 Oct 2018 19:39:48 +1000 Subject: [PATCH 048/149] Add css scroll-snap-type --- src/extensions/default/CSSCodeHints/CSSProperties.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extensions/default/CSSCodeHints/CSSProperties.json b/src/extensions/default/CSSCodeHints/CSSProperties.json index 7b80dbca158..ba6b343d154 100644 --- a/src/extensions/default/CSSCodeHints/CSSProperties.json +++ b/src/extensions/default/CSSCodeHints/CSSProperties.json @@ -190,6 +190,7 @@ "resize": {"values": ["both", "horizontal", "none", "vertical", "inherit"]}, "right": {"values": ["auto", "inherit"]}, "scroll-behavior": {"values": ["auto", "smooth"]}, + "scroll-snap-type": {"values": ["none", "x", "y", "block", "inline", "both", "mandatory", "proximity"]}, "src": {"values": [ "url()"]}, "shape-image-threshold": {"values": []}, "shape-inside": {"values": ["auto", "circle()", "ellipse()", "inherit", "outside-shape", "polygon()", "rectangle()"]}, From 566827fbcaaa000b07fa961f7ebe8545aab86c98 Mon Sep 17 00:00:00 2001 From: Alejandro Bar Acedo Date: Sun, 7 Oct 2018 09:41:50 +1100 Subject: [PATCH 049/149] Add autocompletion for justify-items and justify-self --- src/extensions/default/CSSCodeHints/CSSProperties.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extensions/default/CSSCodeHints/CSSProperties.json b/src/extensions/default/CSSCodeHints/CSSProperties.json index 7b80dbca158..413307f1237 100644 --- a/src/extensions/default/CSSCodeHints/CSSProperties.json +++ b/src/extensions/default/CSSCodeHints/CSSProperties.json @@ -140,6 +140,8 @@ "image-resolution": {"values": ["from-image", "snap"]}, "isolation": {"values": ["auto", "isolate"]}, "justify-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between"]}, + "justify-items": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial", "unset"]}, + "justity-self": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial", "unset"]}, "left": {"values": ["auto", "inherit"]}, "letter-spacing": {"values": ["normal", "inherit"]}, "line-height": {"values": ["normal", "inherit"]}, From 488edaffa02e7a1ed4a4070721a2a2df172bf2c3 Mon Sep 17 00:00:00 2001 From: Narayani Date: Tue, 16 Oct 2018 13:16:16 +0530 Subject: [PATCH 050/149] Update CSSProperties.json for text-align and text-justify (#14563) * Adding css3 new values start, end, match-parent, justify-all to text-align property and adding the property text-justify with its values. * Update CSSProperties.json * Adding "inherit" value for text-justify --- src/extensions/default/CSSCodeHints/CSSProperties.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/CSSCodeHints/CSSProperties.json b/src/extensions/default/CSSCodeHints/CSSProperties.json index 7b80dbca158..d9bd8370feb 100644 --- a/src/extensions/default/CSSCodeHints/CSSProperties.json +++ b/src/extensions/default/CSSCodeHints/CSSProperties.json @@ -197,7 +197,7 @@ "shape-outside": {"values": ["none", "inherit", "circle()", "ellipse()", "polygon()", "inset()", "margin-box", "border-box", "padding-box", "content-box", "url()", "image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()"]}, "tab-size": {"values": []}, "table-layout": {"values": ["auto", "fixed", "inherit"]}, - "text-align": {"values": ["center", "left", "justify", "right", "inherit"]}, + "text-align": {"values": ["start", "end", "center", "left", "justify", "right", "match-parent", "justify-all", "inherit"]}, "text-align-last": {"values": ["center", "left", "justify", "right", "inherit"]}, "text-decoration": {"values": ["line-through", "none", "overline", "underline", "inherit"]}, "text-decoration-color": {"values": [], "type": "color"}, @@ -209,6 +209,7 @@ "text-emphasis-position": {"values": ["above", "below", "left", "right"]}, "text-emphasis-style": {"values": ["circle", "dot", "double-circle", "filled", "none", "open", "sesame", "triangle"]}, "text-indent": {"values": ["inherit"]}, + "text-justify": {"values": ["auto", "none", "inter-word", "inter-character", "inherit"]}, "text-overflow": {"values": ["clip", "ellipsis", "inherit"]}, "text-shadow": {"values": []}, "text-rendering": {"values": ["auto", "geometricPrecision", "optimizeLegibility", "optimizeSpeed"]}, From 7479c0356fc5205dc82514b8f6080159bf080726 Mon Sep 17 00:00:00 2001 From: Prashanth Nethi Date: Mon, 3 Dec 2018 11:34:51 +0530 Subject: [PATCH 051/149] Added error checks to the auto update mechanism. So in case the auto update mechansim fails, we will now give chance to the default update process Handler to handle the update mechanism (Which is essentially taking the user to brackets.io). (#14605) --- src/extensions/default/AutoUpdate/main.js | 4 ++-- src/utils/UpdateNotification.js | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index da8eff67c4b..a4fa83f1411 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -486,12 +486,12 @@ define(function (require, exports, module) { } else { // Update not present for current platform - return; + return false; } if (!checksum || !downloadURL || !installerName) { console.warn("AutoUpdate : asset information incorrect for the update"); - return; + return false; } var updateParams = { diff --git a/src/utils/UpdateNotification.js b/src/utils/UpdateNotification.js index 8bef34b0ac7..dd012e4027c 100644 --- a/src/utils/UpdateNotification.js +++ b/src/utils/UpdateNotification.js @@ -512,7 +512,12 @@ define(function (require, exports, module) { */ function handleUpdateProcess(updates) { var handler = _updateProcessHandler || _defaultUpdateProcessHandler; - handler(updates); + var success = handler(updates); + if (_updateProcessHandler && !success) { + // Give a chance to default handler in case + // the auot update mechanism has failed. + _defaultUpdateProcessHandler(updates); + } } /** From 6cd2092aceaf9d6be6e321918d82c39b321dacca Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Tue, 5 Mar 2019 13:58:39 +0530 Subject: [PATCH 052/149] Update notification - based on current platform (#14655) * Update notification - based on current platform * Fix lint error in Global.js * Restructure validity check and remove duplicate function definition --- src/extensions/default/AutoUpdate/main.js | 23 +---------------------- src/utils/Global.js | 18 ++++++++++++++++++ src/utils/UpdateNotification.js | 20 +++++++++++++++----- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index a4fa83f1411..0f0d4f41bf7 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -329,27 +329,6 @@ define(function (require, exports, module) { } - /** - * Generates the extension for installer file, based on platform - * @returns {string} - OS - current OS } - */ - function getPlatformInfo() { - var OS = ""; - - if (/Windows|Win32|WOW64|Win64/.test(window.navigator.userAgent)) { - OS = "WIN"; - } else if (/Mac/.test(window.navigator.userAgent)) { - OS = "OSX"; - } else if (/Linux|X11/.test(window.navigator.userAgent)) { - OS = "LINUX32"; - if (/x86_64/.test(window.navigator.appVersion + window.navigator.userAgent)) { - OS = "LINUX64"; - } - } - - return OS; - } - /** * Initializes the state for AutoUpdate process * @returns {$.Deferred} - a jquery promise, @@ -466,7 +445,7 @@ define(function (require, exports, module) { console.warn("AutoUpdate : updates information not available."); return; } - var OS = getPlatformInfo(), + var OS = brackets.getPlatformInfo(), checksum, downloadURL, installerName, diff --git a/src/utils/Global.js b/src/utils/Global.js index 76c85cfdf1d..69a9189799c 100644 --- a/src/utils/Global.js +++ b/src/utils/Global.js @@ -83,6 +83,24 @@ define(function (require, exports, module) { global.brackets.platform = "win"; } + // Expose platform info for build applicability consumption + global.brackets.getPlatformInfo = function () { + var OS = ""; + + if (/Windows|Win32|WOW64|Win64/.test(window.navigator.userAgent)) { + OS = "WIN"; + } else if (/Mac/.test(window.navigator.userAgent)) { + OS = "OSX"; + } else if (/Linux|X11/.test(window.navigator.userAgent)) { + OS = "LINUX32"; + if (/x86_64/.test(window.navigator.appVersion + window.navigator.userAgent)) { + OS = "LINUX64"; + } + } + + return OS; + }; + global.brackets.inBrowser = !global.brackets.hasOwnProperty("fs"); // Are we in a desktop shell with a native menu bar? diff --git a/src/utils/UpdateNotification.js b/src/utils/UpdateNotification.js index dd012e4027c..cdd05f6067e 100644 --- a/src/utils/UpdateNotification.js +++ b/src/utils/UpdateNotification.js @@ -261,6 +261,13 @@ define(function (require, exports, module) { return result.promise(); } + /** + * Checks whether a build is applicable to the current platform. + */ + function _checkBuildApplicability(buildInfo) { + return !buildInfo.platforms || buildInfo.platforms[brackets.getPlatformInfo()]; + } + /** * Return a new array of version information that is newer than "buildNumber". * Returns null if there is no new version information. @@ -270,20 +277,23 @@ define(function (require, exports, module) { // should get through the search quickly. var lastIndex = 0; var len = versionInfo.length; + var versionEntry; + var validBuildEntries; while (lastIndex < len) { - if (versionInfo[lastIndex].buildNumber <= buildNumber) { + versionEntry = versionInfo[lastIndex]; + if (versionEntry.buildNumber <= buildNumber) { break; } lastIndex++; } if (lastIndex > 0) { - return versionInfo.slice(0, lastIndex); + // Filter recent update entries based on applicability to current platform + validBuildEntries = versionInfo.slice(0, lastIndex).filter(_checkBuildApplicability); } - // No new version info - return null; + return validBuildEntries; } /** @@ -446,7 +456,7 @@ define(function (require, exports, module) { return; } - if (allUpdates) { + if (allUpdates && allUpdates.length > 0) { // Always show the "update available" icon if any updates are available var $updateNotification = $("#update-notification"); From df7aea423a1e554f00d317078b5296870241cae2 Mon Sep 17 00:00:00 2001 From: Subhash Jha Date: Tue, 2 Apr 2019 15:05:43 +0530 Subject: [PATCH 053/149] Language Server Protocol Support for Brackets (#14606) * LSP Initial set of changes * Adding comments and a bit of cleanup * Adding php client for lsp * further cleanup * removing dependency on HintUtils * removing phpClient extension from this branch * Cleanup * fixing eslint errors * Refactoring code- Removing dependency on JSUtils ANd adding basic structure for client capabilities * Bug Fix: too many listeners were getting attached to node process + code cleanup * putting null check and settign capabilities to default values * reinitializing server on workspace change and moving out capabilities from client code * cleanup * First cut for LSP support in Brackets * First cut for LSP support in Brackets * Adding client infrastructure * Adding client infrastructure * Adding handlers on Language Client Proxy, fixing eslint errors * Adding handlers on Language Client Proxy, fixing eslint errors * Fixing protocol adapter * Fixing protocol adapter * Fix typo * Fix typo * Removing older implementation * Removing older implementation * Added error checks to the auto update mechanism. So in case the auto update mechansim fails, we will now give chance to the default update process Handler to handle the update mechanism (Which is essentially taking the user to brackets.io). (#14605) * First cut for LSP support in Brackets * First cut for LSP support in Brackets * Adding client infrastructure * Adding client infrastructure * Adding handlers on Language Client Proxy, fixing eslint errors * Adding handlers on Language Client Proxy, fixing eslint errors * Fixing protocol adapter * Fixing protocol adapter * Fix typo * Fix typo * Removing older implementation * Removing older implementation * Removing custom comments * Removing custom comments * Fixing Typo * Fixing Typo * Add missing Params in function call * Add missing Params in function call * Correcting message type, handlers * Correcting message type, handlers * Minor correction on active project change * Minor correction on active project change * Correcting the message format for didChange * Correcting the message format for didChange * Changing custom notification and request handlers, correcting typo, adding catch block for Connection * Changing custom notification and request handlers, correcting typo, adding catch block for Connection * Stop Creation of Multiple Language Servers * Stop Creation of Multiple Language Servers * Make Language Client Generic, address review comments * Make Language Client Generic, address review comments * Correcting param descriptions * Correcting param descriptions * Modifying events handling logic for Language Client, add formatting option for communication params * Modifying events handling logic for Language Client, add formatting option for communication params * Add handlers for node side * Add handlers for node side * Removing explicit param creation, substituting with appropriate checks * Removing explicit param creation, substituting with appropriate checks * Fixing lint errors in MessageHandler.js * Fixing lint errors in MessageHandler.js * Messaging related cleanup * Messaging related cleanup * Adding default providers and feature managers * Adding default providers and feature managers * Adding banner and fixing lint error * Adding banner and fixing lint error * fix spacing issue * fix spacing issue * Fix spacing issues * Fix spacing issues * Add filetype checks for all events, minor server info corrections * Add filetype checks for all events, minor server info corrections * Handling Reload with Extension Scenario, minor JumpToDef provider fix * Handling Reload with Extension Scenario, minor JumpToDef provider fix * Correcting Typo * Correcting Typo * Adding bug fixes * Adding bug fixes * Adding bug fixes 2 * Adding bug fixes 2 * Addressing Review: Fixing minor typo * Addressing Review: Fixing minor typo * Minor bug fixes, functionality enhancements * Minor bug fixes, functionality enhancements * Adding tests for Language Server Support: first cut * Adding tests for Language Server Support: first cut * Adding banner, fixing lint errors * Adding banner, fixing lint errors * Adding dependency related tasks * Adding dependency related tasks * Fixing npm environment string * Fixing npm environment string * Changing handler name * Changing handler name * Changing file name to ClientLoader * Changing file name to ClientLoader * Changing variable name appropriately * Changing variable name appropriately * Grunt related changes for build * Grunt related changes for build * Adding additional requests and notifications for handling various scenarios * Adding additional requests and notifications for handling various scenarios * Adding Path Converter Utilities * Adding Path Converter Utilities * Changing Ternary operator to OR operater * Changing Ternary operator to OR operater * Addressing review comments * Addressing review comments * Removing the handler for editor change, will be handled explicitely * Removing the handler for editor change, will be handled explicitely * Patching JavaScriptCodeHints * Patching JavaScriptCodeHints * Preferences infra for LanguageTools * Preferences infra for LanguageTools * Fixing JS ParameterHints * Fixing JS ParameterHints * Fixing Default Parameter Hints Provider * Fixing Default Parameter Hints Provider * Fixing Path Converters * Fixing Path Converters * Fixing Lint in PathConverters * Fixing Lint in PathConverters * Retaining Posix Path on Win * Retaining Posix Path on Win * Fixing lint errors * Fixing lint errors * Fixing Node side Utils * Fixing Node side Utils * Fixing Promise related Issues * Fixing Promise related Issues * Set Server Capability in Start call * Set Server Capability in Start call * Review Comments & Bug Fixes * Review Comments & Bug Fixes * Addressing Review Comments * Addressing Review Comments * Fixing Lint * Fixing Lint --- .eslintrc.js | 10 + Gruntfile.js | 3 + src/brackets.js | 13 + src/command/Commands.js | 2 +- src/editor/EditorManager.js | 76 - .../ParameterHintManager.js | 445 ----- .../ParameterHintTemplate.html | 4 - .../ParameterHintsProvider.js | 229 +++ .../default/JavaScriptCodeHints/keyboard.json | 11 - .../default/JavaScriptCodeHints/main.js | 69 +- .../default/JavaScriptCodeHints/unittests.js | 79 +- .../default/JavaScriptQuickEdit/unittests.js | 64 +- src/features/JumpToDefManager.js | 89 + src/features/ParameterHintsManager.js | 416 +++++ src/features/PriorityBasedRegistration.js | 138 ++ src/htmlContent/parameter-hint-template.html | 4 + src/languageTools/BracketsToNodeInterface.js | 121 ++ src/languageTools/ClientLoader.js | 128 ++ src/languageTools/DefaultEventHandlers.js | 193 ++ src/languageTools/DefaultProviders.js | 384 ++++ .../LanguageClient/Connection.js | 134 ++ .../LanguageClient/LanguageClient.js | 232 +++ .../LanguageClient/NodeToBracketsInterface.js | 213 +++ .../LanguageClient/ProtocolAdapter.js | 398 ++++ .../LanguageClient/ServerUtils.js | 427 +++++ src/languageTools/LanguageClient/Utils.js | 88 + src/languageTools/LanguageClient/package.json | 19 + src/languageTools/LanguageClientWrapper.js | 627 +++++++ src/languageTools/LanguageTools.js | 119 ++ src/languageTools/PathConverters.js | 82 + src/languageTools/ToolingInfo.json | 41 + .../node/RegisterLanguageClientInfo.js | 290 +++ .../styles/default_provider_style.css | 134 ++ src/nls/root/strings.js | 5 +- tasks/npm-install.js | 35 +- test/SpecRunner.js | 12 + test/UnitTestSuite.js | 1 + .../clients/CommunicationTestClient/client.js | 121 ++ .../clients/CommunicationTestClient/main.js | 68 + .../CommunicationTestClient/package.json | 5 + .../clients/FeatureClient/client.js | 73 + .../clients/FeatureClient/main.js | 74 + .../clients/InterfaceTestClient/client.js | 130 ++ .../clients/InterfaceTestClient/main.js | 59 + .../clients/LoadSimpleClient/client.js | 37 + .../clients/LoadSimpleClient/main.js | 51 + .../clients/ModuleTestClient/client.js | 73 + .../clients/ModuleTestClient/main.js | 74 + .../clients/OptionsTestClient/client.js | 145 ++ .../clients/OptionsTestClient/main.js | 68 + .../project/sample1.txt | 1 + .../project/sample2.txt | 1 + .../server/lsp-test-server/main.js | 255 +++ .../server/lsp-test-server/package.json | 5 + test/spec/LanguageTools-test.js | 1599 +++++++++++++++++ 55 files changed, 7527 insertions(+), 647 deletions(-) delete mode 100644 src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js delete mode 100644 src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html create mode 100644 src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js delete mode 100644 src/extensions/default/JavaScriptCodeHints/keyboard.json create mode 100644 src/features/JumpToDefManager.js create mode 100644 src/features/ParameterHintsManager.js create mode 100644 src/features/PriorityBasedRegistration.js create mode 100644 src/htmlContent/parameter-hint-template.html create mode 100644 src/languageTools/BracketsToNodeInterface.js create mode 100644 src/languageTools/ClientLoader.js create mode 100644 src/languageTools/DefaultEventHandlers.js create mode 100644 src/languageTools/DefaultProviders.js create mode 100644 src/languageTools/LanguageClient/Connection.js create mode 100644 src/languageTools/LanguageClient/LanguageClient.js create mode 100644 src/languageTools/LanguageClient/NodeToBracketsInterface.js create mode 100644 src/languageTools/LanguageClient/ProtocolAdapter.js create mode 100644 src/languageTools/LanguageClient/ServerUtils.js create mode 100644 src/languageTools/LanguageClient/Utils.js create mode 100644 src/languageTools/LanguageClient/package.json create mode 100644 src/languageTools/LanguageClientWrapper.js create mode 100644 src/languageTools/LanguageTools.js create mode 100644 src/languageTools/PathConverters.js create mode 100644 src/languageTools/ToolingInfo.json create mode 100644 src/languageTools/node/RegisterLanguageClientInfo.js create mode 100644 src/languageTools/styles/default_provider_style.css create mode 100644 test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json create mode 100644 test/spec/LanguageTools-test-files/clients/FeatureClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/FeatureClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/project/sample1.txt create mode 100644 test/spec/LanguageTools-test-files/project/sample2.txt create mode 100644 test/spec/LanguageTools-test-files/server/lsp-test-server/main.js create mode 100644 test/spec/LanguageTools-test-files/server/lsp-test-server/package.json create mode 100644 test/spec/LanguageTools-test.js diff --git a/.eslintrc.js b/.eslintrc.js index 40331166ab1..4ff0306efcb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -80,5 +80,15 @@ module.exports = { "Uint32Array": false, "WebSocket": false, "XMLHttpRequest": false + }, + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "script", + "ecmaFeatures": { + "arrowFunctions": true, + "binaryLiterals": true, + "blockBindings": true, + "classes": true + } } }; diff --git a/Gruntfile.js b/Gruntfile.js index fe7e2d0a1d0..0a276991b8c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -82,6 +82,9 @@ module.exports = function (grunt) { src: [ 'extensibility/node/**', 'JSUtils/node/**', + 'languageTools/node/**', + 'languageTools/styles/**', + 'languageTools/LanguageClient/**', '!extensibility/node/spec/**', '!extensibility/node/node_modules/**/{test,tst}/**/*', '!extensibility/node/node_modules/**/examples/**/*', diff --git a/src/brackets.js b/src/brackets.js index 9a21072cefd..4365249ef05 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -137,6 +137,10 @@ define(function (require, exports, module) { return PathUtils; } }); + + //load language features + require("features/ParameterHintsManager"); + require("features/JumpToDefManager"); // Load modules that self-register and just need to get included in the main project require("command/DefaultMenus"); @@ -155,6 +159,15 @@ define(function (require, exports, module) { require("JSUtils/Session"); require("JSUtils/ScopeManager"); + //load Language Tools Module + require("languageTools/PathConverters"); + require("languageTools/LanguageTools"); + require("languageTools/ClientLoader"); + require("languageTools/BracketsToNodeInterface"); + require("languageTools/DefaultProviders"); + require("languageTools/DefaultEventHandlers"); + + PerfUtils.addMeasurement("brackets module dependencies resolved"); // Local variables diff --git a/src/command/Commands.js b/src/command/Commands.js index e607880457e..dc147eeac6f 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -123,7 +123,7 @@ define(function (require, exports, module) { exports.NAVIGATE_SHOW_IN_FILE_TREE = "navigate.showInFileTree"; // DocumentCommandHandlers.js handleShowInTree() exports.NAVIGATE_SHOW_IN_OS = "navigate.showInOS"; // DocumentCommandHandlers.js handleShowInOS() exports.NAVIGATE_QUICK_OPEN = "navigate.quickOpen"; // QuickOpen.js doFileSearch() - exports.NAVIGATE_JUMPTO_DEFINITION = "navigate.jumptoDefinition"; // EditorManager.js _doJumpToDef() + exports.NAVIGATE_JUMPTO_DEFINITION = "navigate.jumptoDefinition"; // JumpToDefManager.js _doJumpToDef() exports.NAVIGATE_GOTO_DEFINITION = "navigate.gotoDefinition"; // QuickOpen.js doDefinitionSearch() exports.NAVIGATE_GOTO_LINE = "navigate.gotoLine"; // QuickOpen.js doGotoLine() exports.NAVIGATE_GOTO_FIRST_PROBLEM = "navigate.gotoFirstProblem"; // CodeInspection.js handleGotoFirstProblem() diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index 79897d47118..e9df922d40b 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -92,15 +92,6 @@ define(function (require, exports, module) { */ var _inlineDocsProviders = []; - /** - * Registered jump-to-definition providers. - * @see {@link #registerJumpToDefProvider}. - * @private - * @type {Array.} - */ - var _jumpToDefProviders = []; - - /** * DOM element to house any hidden editors created soley for inline widgets * @private @@ -423,19 +414,6 @@ define(function (require, exports, module) { _insertProviderSorted(_inlineDocsProviders, provider, priority); } - /** - * Registers a new jump-to-definition provider. When jump-to-definition is invoked each - * registered provider is asked if it wants to provide jump-to-definition results, given - * the current editor and cursor location. - * - * @param {function(!Editor, !{line:number, ch:number}):?$.Promise} provider - * The provider returns a promise that is resolved whenever it's done handling the operation, - * or returns null to indicate the provider doesn't want to respond to this case. It is entirely - * up to the provider to open the file containing the definition, select the appropriate text, etc. - */ - function registerJumpToDefProvider(provider) { - _jumpToDefProviders.push(provider); - } /** * @private @@ -705,55 +683,6 @@ define(function (require, exports, module) { return _lastFocusedEditor; } - - /** - * Asynchronously asks providers to handle jump-to-definition. - * @return {!Promise} Resolved when the provider signals that it's done; rejected if no - * provider responded or the provider that responded failed. - */ - function _doJumpToDef() { - var providers = _jumpToDefProviders; - var promise, - i, - result = new $.Deferred(); - - var editor = getActiveEditor(); - - if (editor) { - var pos = editor.getCursorPos(); - - PerfUtils.markStart(PerfUtils.JUMP_TO_DEFINITION); - - // Run through providers until one responds - for (i = 0; i < providers.length && !promise; i++) { - var provider = providers[i]; - promise = provider(editor, pos); - } - - // Will one of them will provide a result? - if (promise) { - promise.done(function () { - PerfUtils.addMeasurement(PerfUtils.JUMP_TO_DEFINITION); - result.resolve(); - }).fail(function () { - // terminate timer that was started above - PerfUtils.finalizeMeasurement(PerfUtils.JUMP_TO_DEFINITION); - result.reject(); - }); - } else { - // terminate timer that was started above - PerfUtils.finalizeMeasurement(PerfUtils.JUMP_TO_DEFINITION); - result.reject(); - } - - } else { - result.reject(); - } - - return result.promise(); - } - - /** * file removed from pane handler. * @param {jQuery.Event} e @@ -797,10 +726,6 @@ define(function (require, exports, module) { CommandManager.register(Strings.CMD_TOGGLE_QUICK_DOCS, Commands.TOGGLE_QUICK_DOCS, function () { return _toggleInlineWidget(_inlineDocsProviders, Strings.ERROR_QUICK_DOCS_PROVIDER_NOT_FOUND); }); - CommandManager.register(Strings.CMD_JUMPTO_DEFINITION, Commands.NAVIGATE_JUMPTO_DEFINITION, _doJumpToDef); - - // Create PerfUtils measurement - PerfUtils.createPerfMeasurement("JUMP_TO_DEFINITION", "Jump-To-Definiiton"); MainViewManager.on("currentFileChange", _handleCurrentFileChange); MainViewManager.on("workingSetRemove workingSetRemoveList", _handleRemoveFromPaneView); @@ -830,7 +755,6 @@ define(function (require, exports, module) { exports.registerInlineEditProvider = registerInlineEditProvider; exports.registerInlineDocsProvider = registerInlineDocsProvider; - exports.registerJumpToDefProvider = registerJumpToDefProvider; // Deprecated exports.registerCustomViewer = registerCustomViewer; diff --git a/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js b/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js deleted file mode 100644 index ce84b9cea7d..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Copyright (c) 2013 - present 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. - * - */ - -define(function (require, exports, module) { - "use strict"; - - var _ = brackets.getModule("thirdparty/lodash"); - - var Commands = brackets.getModule("command/Commands"), - CommandManager = brackets.getModule("command/CommandManager"), - KeyEvent = brackets.getModule("utils/KeyEvent"), - Menus = brackets.getModule("command/Menus"), - Strings = brackets.getModule("strings"), - HintsUtils2 = require("HintUtils2"), - ScopeManager = brackets.getModule("JSUtils/ScopeManager"); - - - /** @const {string} Show Function Hint command ID */ - var SHOW_PARAMETER_HINT_CMD_ID = "showParameterHint", // string must MATCH string in native code (brackets_extensions) - PUSH_EXISTING_HINT = true, - OVERWRITE_EXISTING_HINT = false, - hintContainerHTML = require("text!ParameterHintTemplate.html"), - KeyboardPrefs = JSON.parse(require("text!keyboard.json")); - - var $hintContainer, // function hint container - $hintContent, // function hint content holder - - /** @type {{inFunctionCall: boolean, functionCallPos: {line: number, ch: number}, - * fnType: Array.") - .append(_.escape(param)) - .addClass("current-parameter")); - } else { - $hintContent.append(_.escape(param)); - } - } - - if (hints.parameters.length > 0) { - HintsUtils2.formatParameterHint(hints.parameters, appendSeparators, appendParameter); - } else { - $hintContent.append(_.escape(Strings.NO_ARGUMENTS)); - } - } - - /** - * Save the state of the current hint. Called when popping up a parameter hint - * for a parameter, when the parameter already part of an existing parameter - * hint. - */ - function pushHintOnStack() { - hintStack.push(hintState); - } - - /** - * Restore the state of the previous function hint. - * - * @return {boolean} - true the a parameter hint has been popped, false otherwise. - */ - function popHintFromStack() { - if (hintStack.length > 0) { - hintState = hintStack.pop(); - hintState.visible = false; - return true; - } - - return false; - } - - /** - * Reset the function hint stack. - */ - function clearFunctionHintStack() { - hintStack = []; - } - - /** - * Test if the function call at the cursor is different from the currently displayed - * function hint. - * - * @param {{line:number, ch:number}} functionCallPos - the offset of the function call. - * @return {boolean} - */ - function hasFunctionCallPosChanged(functionCallPos) { - var oldFunctionCallPos = hintState.functionCallPos; - return (oldFunctionCallPos === undefined || - oldFunctionCallPos.line !== functionCallPos.line || - oldFunctionCallPos.ch !== functionCallPos.ch); - } - - /** - * Dismiss the function hint. - * - */ - function dismissHint() { - - if (hintState.visible) { - $hintContainer.hide(); - $hintContent.empty(); - hintState = {}; - session.editor.off("cursorActivity", handleCursorActivity); - - if (!preserveHintStack) { - clearFunctionHintStack(); - } - } - } - - /** - * Pop up a function hint on the line above the caret position. - * - * @param {boolean=} pushExistingHint - if true, push the existing hint on the stack. Default is false, not - * to push the hint. - * @param {string=} hint - function hint string from tern. - * @param {{inFunctionCall: boolean, functionCallPos: - * {line: number, ch: number}}=} functionInfo - - * if the functionInfo is already known, it can be passed in to avoid - * figuring it out again. - * @return {jQuery.Promise} - The promise will not complete until the - * hint has completed. Returns null, if the function hint is already - * displayed or there is no function hint at the cursor. - * - */ - function popUpHint(pushExistingHint, hint, functionInfo) { - - functionInfo = functionInfo || session.getFunctionInfo(); - if (!functionInfo.inFunctionCall) { - dismissHint(); - return null; - } - - if (hasFunctionCallPosChanged(functionInfo.functionCallPos)) { - - var pushHint = pushExistingHint && isHintDisplayed(); - if (pushHint) { - pushHintOnStack(); - preserveHintStack = true; - } - - dismissHint(); - preserveHintStack = false; - } else if (isHintDisplayed()) { - return null; - } - - hintState.functionCallPos = functionInfo.functionCallPos; - - var request = null; - var $deferredPopUp = $.Deferred(); - - if (!hint) { - request = ScopeManager.requestParameterHint(session, functionInfo.functionCallPos); - } else { - session.setFnType(hint); - request = $.Deferred(); - request.resolveWith(null, [hint]); - $deferredPopUp.resolveWith(null); - } - - request.done(function (fnType) { - var cm = session.editor._codeMirror, - pos = cm.charCoords(functionInfo.functionCallPos); - - formatHint(functionInfo); - - $hintContainer.show(); - positionHint(pos.left, pos.top, pos.bottom); - hintState.visible = true; - hintState.fnType = fnType; - - session.editor.on("cursorActivity", handleCursorActivity); - $deferredPopUp.resolveWith(null); - }).fail(function () { - hintState = {}; - }); - - return $deferredPopUp; - } - - /** - * Pop up a function hint on the line above the caret position if the character before - * the current cursor is an open parenthesis - * - * @return {jQuery.Promise} - The promise will not complete until the - * hint has completed. Returns null, if the function hint is already - * displayed or there is no function hint at the cursor. - */ - function popUpHintAtOpenParen() { - var functionInfo = session.getFunctionInfo(); - if (functionInfo.inFunctionCall) { - var token = session.getToken(); - - if (token && token.string === "(") { - return popUpHint(); - } - } else { - dismissHint(); - } - - return null; - } - - /** - * Show the parameter the cursor is on in bold when the cursor moves. - * Dismiss the pop up when the cursor moves off the function. - */ - handleCursorActivity = function () { - var functionInfo = session.getFunctionInfo(); - - if (functionInfo.inFunctionCall) { - // If in a different function hint, then dismiss the old one and - // display the new one if there is one on the stack - if (hasFunctionCallPosChanged(functionInfo.functionCallPos)) { - if (popHintFromStack()) { - var poppedFunctionCallPos = hintState.functionCallPos, - currentFunctionCallPos = functionInfo.functionCallPos; - - if (poppedFunctionCallPos.line === currentFunctionCallPos.line && - poppedFunctionCallPos.ch === currentFunctionCallPos.ch) { - preserveHintStack = true; - popUpHint(OVERWRITE_EXISTING_HINT, - hintState.fnType, functionInfo); - preserveHintStack = false; - return; - } - } else { - dismissHint(); - } - } - - formatHint(functionInfo); - return; - } - - dismissHint(); - }; - - /** - * Enable cursor tracking in the current session. - * - * @param {Session} session - session to start cursor tracking on. - */ - function startCursorTracking(session) { - session.editor.on("cursorActivity", handleCursorActivity); - } - - /** - * Stop cursor tracking in the current session. - * - * Use this to move the cursor without changing the function hint state. - * - * @param {Session} session - session to stop cursor tracking on. - */ - function stopCursorTracking(session) { - session.editor.off("cursorActivity", handleCursorActivity); - } - - /** - * Show a parameter hint in its own pop-up. - * - */ - function handleShowParameterHint() { - - // Pop up function hint - popUpHint(); - } - - /** - * Install function hint listeners. - * - * @param {Editor} editor - editor context on which to listen for - * changes - */ - function installListeners(editor) { - editor.on("keydown.ParameterHints", function (event, editor, domEvent) { - if (domEvent.keyCode === KeyEvent.DOM_VK_ESCAPE) { - dismissHint(); - } - }).on("scroll.ParameterHints", function () { - dismissHint(); - }); - } - - /** - * Clean up after installListeners() - * @param {!Editor} editor - */ - function uninstallListeners(editor) { - editor.off(".ParameterHints"); - } - - /** - * Add the function hint command at start up. - */ - function addCommands() { - /* Register the command handler */ - CommandManager.register(Strings.CMD_SHOW_PARAMETER_HINT, SHOW_PARAMETER_HINT_CMD_ID, handleShowParameterHint); - - // Add the menu items - var menu = Menus.getMenu(Menus.AppMenuBar.EDIT_MENU); - if (menu) { - menu.addMenuItem(SHOW_PARAMETER_HINT_CMD_ID, KeyboardPrefs.showParameterHint, Menus.AFTER, Commands.SHOW_CODE_HINTS); - } - - // Close the function hint when commands are executed, except for the commands - // to show function hints for code hints. - CommandManager.on("beforeExecuteCommand", function (event, commandId) { - if (commandId !== SHOW_PARAMETER_HINT_CMD_ID && - commandId !== Commands.SHOW_CODE_HINTS) { - dismissHint(); - } - }); - } - - // Create the function hint container - $hintContainer = $(hintContainerHTML).appendTo($("body")); - $hintContent = $hintContainer.find(".function-hint-content"); - - exports.PUSH_EXISTING_HINT = PUSH_EXISTING_HINT; - exports.addCommands = addCommands; - exports.dismissHint = dismissHint; - exports.installListeners = installListeners; - exports.uninstallListeners = uninstallListeners; - exports.isHintDisplayed = isHintDisplayed; - exports.popUpHint = popUpHint; - exports.popUpHintAtOpenParen = popUpHintAtOpenParen; - exports.setSession = setSession; - exports.startCursorTracking = startCursorTracking; - exports.stopCursorTracking = stopCursorTracking; - -}); diff --git a/src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html b/src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html deleted file mode 100644 index 04f8a9c04a2..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
diff --git a/src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js b/src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js new file mode 100644 index 00000000000..72c9d27ac73 --- /dev/null +++ b/src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2013 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var ScopeManager = brackets.getModule("JSUtils/ScopeManager"), + OVERWRITE_EXISTING_HINT = false; + + function JSParameterHintsProvider() { + this.hintState = {}; + this.hintStack = []; + this.preserveHintStack = null; // close a function hint without clearing stack + this.session = null; // current editor session, updated by main + } + + /** + * Update the current session for use by the Function Hint Manager. + * + * @param {Session} value - current session. + */ + JSParameterHintsProvider.prototype.setSession = function (value) { + this.session = value; + }; + + /** + * Test if a function hint is being displayed. + * + * @return {boolean} - true if a function hint is being displayed, false + * otherwise. + */ + JSParameterHintsProvider.prototype.isHintDisplayed = function () { + return this.hintState.visible === true; + }; + + /** + * Save the state of the current hint. Called when popping up a parameter hint + * for a parameter, when the parameter already part of an existing parameter + * hint. + */ + JSParameterHintsProvider.prototype.pushHintOnStack = function () { + this.hintStack.push(this.hintState); + }; + + /** + * Restore the state of the previous function hint. + * + * @return {boolean} - true the a parameter hint has been popped, false otherwise. + */ + JSParameterHintsProvider.prototype.popHintFromStack = function () { + if (this.hintStack.length > 0) { + this.hintState = this.hintStack.pop(); + this.hintState.visible = false; + return true; + } + + return false; + }; + + /** + * Reset the function hint stack. + */ + JSParameterHintsProvider.prototype.clearFunctionHintStack = function () { + this.hintStack = []; + }; + + /** + * Test if the function call at the cursor is different from the currently displayed + * function hint. + * + * @param {{line:number, ch:number}} functionCallPos - the offset of the function call. + * @return {boolean} + */ + JSParameterHintsProvider.prototype.hasFunctionCallPosChanged = function (functionCallPos) { + var oldFunctionCallPos = this.hintState.functionCallPos; + return (oldFunctionCallPos === undefined || + oldFunctionCallPos.line !== functionCallPos.line || + oldFunctionCallPos.ch !== functionCallPos.ch); + }; + + /** + * Dismiss the function hint. + * + */ + JSParameterHintsProvider.prototype.cleanHintState = function () { + if (this.hintState.visible) { + if (!this.preserveHintStack) { + this.clearFunctionHintStack(); + } + } + }; + + /** + * Pop up a function hint on the line above the caret position. + * + * @param {boolean=} pushExistingHint - if true, push the existing hint on the stack. Default is false, not + * to push the hint. + * @param {string=} hint - function hint string from tern. + * @param {{inFunctionCall: boolean, functionCallPos: + * {line: number, ch: number}}=} functionInfo - + * if the functionInfo is already known, it can be passed in to avoid + * figuring it out again. + * @return {jQuery.Promise} - The promise will not complete until the + * hint has completed. Returns null, if the function hint is already + * displayed or there is no function hint at the cursor. + * + */ + JSParameterHintsProvider.prototype._getParameterHint = function (pushExistingHint, hint, functionInfo) { + var result = $.Deferred(); + functionInfo = functionInfo || this.session.getFunctionInfo(); + if (!functionInfo.inFunctionCall) { + this.cleanHintState(); + return result.reject(null); + } + + if (this.hasFunctionCallPosChanged(functionInfo.functionCallPos)) { + + var pushHint = pushExistingHint && this.isHintDisplayed(); + if (pushHint) { + this.pushHintOnStack(); + this.preserveHintStack = true; + } + + this.cleanHintState(); + this.preserveHintStack = false; + } else if (this.isHintDisplayed()) { + return result.reject(null); + } + + this.hintState.functionCallPos = functionInfo.functionCallPos; + + var request = null; + if (!hint) { + request = ScopeManager.requestParameterHint(this.session, functionInfo.functionCallPos); + } else { + this.session.setFnType(hint); + request = $.Deferred(); + request.resolveWith(null, [hint]); + } + + var self = this; + request.done(function (fnType) { + var hints = self.session.getParameterHint(functionInfo.functionCallPos); + hints.functionCallPos = functionInfo.functionCallPos; + result.resolve(hints); + }).fail(function () { + self.hintState = {}; + result.reject(null); + }); + + return result; + }; + + JSParameterHintsProvider.prototype.hasParameterHints = function () { + var functionInfo = this.session.getFunctionInfo(); + + return functionInfo.inFunctionCall; + }; + + JSParameterHintsProvider.prototype.getParameterHints = function (explicit, onCursorActivity) { + var functionInfo = this.session.getFunctionInfo(), + result = null; + + if (!onCursorActivity) { + if (functionInfo.inFunctionCall) { + var token = this.session.getToken(); + + if ((token && token.string === "(") || explicit) { + return this._getParameterHint(); + } + } else { + this.cleanHintState(); + } + + return $.Deferred().reject(null); + } + + if (!functionInfo.inFunctionCall) { + this.cleanHintState(); + return $.Deferred().reject(null); + } + + // If in a different function hint, then dismiss the old one and + // display the new one if there is one on the stack + if (this.hasFunctionCallPosChanged(functionInfo.functionCallPos)) { + if (this.popHintFromStack()) { + var poppedFunctionCallPos = this.hintState.functionCallPos, + currentFunctionCallPos = this.functionInfo.functionCallPos; + + if (poppedFunctionCallPos.line === currentFunctionCallPos.line && + poppedFunctionCallPos.ch === currentFunctionCallPos.ch) { + this.preserveHintStack = true; + result = this._getParameterHint(OVERWRITE_EXISTING_HINT, + this.hintState.fnType, functionInfo); + this.preserveHintStack = false; + return result; + } + } else { + this.cleanHintState(); + } + } + + var hints = this.session.getParameterHint(functionInfo.functionCallPos); + hints.functionCallPos = functionInfo.functionCallPos; + return $.Deferred().resolve(hints); + }; + + exports.JSParameterHintsProvider = JSParameterHintsProvider; +}); diff --git a/src/extensions/default/JavaScriptCodeHints/keyboard.json b/src/extensions/default/JavaScriptCodeHints/keyboard.json deleted file mode 100644 index d4d4e5d1345..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/keyboard.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "showParameterHint": [ - { - "key": "Ctrl-Shift-Space" - }, - { - "key": "Ctrl-Shift-Space", - "platform": "mac" - } - ] -} \ No newline at end of file diff --git a/src/extensions/default/JavaScriptCodeHints/main.js b/src/extensions/default/JavaScriptCodeHints/main.js index 66b42a022f3..586ec1004c7 100644 --- a/src/extensions/default/JavaScriptCodeHints/main.js +++ b/src/extensions/default/JavaScriptCodeHints/main.js @@ -26,22 +26,24 @@ define(function (require, exports, module) { var _ = brackets.getModule("thirdparty/lodash"); - var CodeHintManager = brackets.getModule("editor/CodeHintManager"), - EditorManager = brackets.getModule("editor/EditorManager"), - Commands = brackets.getModule("command/Commands"), - CommandManager = brackets.getModule("command/CommandManager"), - LanguageManager = brackets.getModule("language/LanguageManager"), - AppInit = brackets.getModule("utils/AppInit"), - ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), - StringMatch = brackets.getModule("utils/StringMatch"), - ProjectManager = brackets.getModule("project/ProjectManager"), - PreferencesManager = brackets.getModule("preferences/PreferencesManager"), - Strings = brackets.getModule("strings"), - ParameterHintManager = require("ParameterHintManager"), - HintUtils = brackets.getModule("JSUtils/HintUtils"), - ScopeManager = brackets.getModule("JSUtils/ScopeManager"), - Session = brackets.getModule("JSUtils/Session"), - Acorn = require("node_modules/acorn/dist/acorn"); + var CodeHintManager = brackets.getModule("editor/CodeHintManager"), + EditorManager = brackets.getModule("editor/EditorManager"), + Commands = brackets.getModule("command/Commands"), + CommandManager = brackets.getModule("command/CommandManager"), + LanguageManager = brackets.getModule("language/LanguageManager"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + StringMatch = brackets.getModule("utils/StringMatch"), + ProjectManager = brackets.getModule("project/ProjectManager"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + Strings = brackets.getModule("strings"), + JSParameterHintsProvider = require("./ParameterHintsProvider").JSParameterHintsProvider, + ParameterHintsManager = brackets.getModule("features/ParameterHintsManager"), + HintUtils = brackets.getModule("JSUtils/HintUtils"), + ScopeManager = brackets.getModule("JSUtils/ScopeManager"), + Session = brackets.getModule("JSUtils/Session"), + JumpToDefManager = brackets.getModule("features/JumpToDefManager"), + Acorn = require("node_modules/acorn/dist/acorn"); var session = null, // object that encapsulates the current session state cachedCursor = null, // last cursor of the current hinting session @@ -55,7 +57,8 @@ define(function (require, exports, module) { ignoreChange; // can ignore next "change" event if true; // Languages that support inline JavaScript - var _inlineScriptLanguages = ["html", "php"]; + var _inlineScriptLanguages = ["html", "php"], + phProvider = new JSParameterHintsProvider(); // Define the detectedExclusions which are files that have been detected to cause Tern to run out of control. PreferencesManager.definePreference("jscodehints.detectedExclusions", "array", [], { @@ -642,7 +645,7 @@ define(function (require, exports, module) { session = new Session(editor); ScopeManager.handleEditorChange(session, editor.document, previousEditor ? previousEditor.document : null); - ParameterHintManager.setSession(session); + phProvider.setSession(session); cachedHints = null; } @@ -667,11 +670,9 @@ define(function (require, exports, module) { .on(HintUtils.eventName("change"), function (event, editor, changeList) { if (!ignoreChange) { ScopeManager.handleFileChange(changeList); - ParameterHintManager.popUpHintAtOpenParen(); } ignoreChange = false; }); - ParameterHintManager.installListeners(editor); } else { session = null; } @@ -686,7 +687,6 @@ define(function (require, exports, module) { function uninstallEditorListeners(editor) { if (editor) { editor.off(HintUtils.eventName("change")); - ParameterHintManager.uninstallListeners(editor); } } @@ -719,10 +719,21 @@ define(function (require, exports, module) { installEditorListeners(current, previous); } - /* - * Handle JumptoDefiniton menu/keyboard command. + function setJumpPosition(curPos) { + EditorManager.getCurrentFullEditor().setCursorPos(curPos.line, curPos.ch, true); + } + + function JSJumpToDefProvider() { + } + + JSJumpToDefProvider.prototype.canJumpToDef = function (editor, implicitChar) { + return true; + }; + + /** + * Method to handle jump to definition feature. */ - function handleJumpToDefinition() { + JSJumpToDefProvider.prototype.doJumpToDef = function () { var offset, handleJumpResponse; @@ -856,7 +867,7 @@ define(function (require, exports, module) { requestJumpToDef(session, offset); return result.promise(); - } + }; /* * Helper for QuickEdit jump-to-definition request. @@ -891,18 +902,18 @@ define(function (require, exports, module) { // immediately install the current editor installEditorListeners(EditorManager.getActiveEditor()); + ParameterHintsManager.registerHintProvider(phProvider, ["javascript"], 0); // init - EditorManager.registerJumpToDefProvider(handleJumpToDefinition); + var jdProvider = new JSJumpToDefProvider(); + JumpToDefManager.registerJumpToDefProvider(jdProvider, ["javascript"], 0); var jsHints = new JSHints(); CodeHintManager.registerHintProvider(jsHints, HintUtils.SUPPORTED_LANGUAGES, 0); - ParameterHintManager.addCommands(); - // for unit testing exports.getSession = getSession; exports.jsHintProvider = jsHints; exports.initializeSession = initializeSession; - exports.handleJumpToDefinition = handleJumpToDefinition; + exports.handleJumpToDefinition = jdProvider.doJumpToDef.bind(jdProvider); }); }); diff --git a/src/extensions/default/JavaScriptCodeHints/unittests.js b/src/extensions/default/JavaScriptCodeHints/unittests.js index e8634d07219..7e31ebb28d2 100644 --- a/src/extensions/default/JavaScriptCodeHints/unittests.js +++ b/src/extensions/default/JavaScriptCodeHints/unittests.js @@ -22,7 +22,7 @@ */ /*jslint regexp: true */ -/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, beforeFirst, afterLast */ +/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, waitsForFail, beforeFirst, afterLast */ define(function (require, exports, module) { "use strict"; @@ -41,7 +41,8 @@ define(function (require, exports, module) { ScopeManager = brackets.getModule("JSUtils/ScopeManager"), HintUtils = brackets.getModule("JSUtils/HintUtils"), HintUtils2 = require("HintUtils2"), - ParameterHintManager = require("ParameterHintManager"); + ParameterHintProvider = require("ParameterHintsProvider").JSParameterHintsProvider, + phProvider = new ParameterHintProvider(); var extensionPath = FileUtils.getNativeModuleDirectoryPath(module), testPath = extensionPath + "/unittest-files/basic-test-files/file1.js", @@ -341,39 +342,26 @@ define(function (require, exports, module) { * Verify there is no parameter hint at the current cursor. */ function expectNoParameterHint() { - expect(ParameterHintManager.popUpHint()).toBe(null); + var requestStatus = undefined; + runs(function () { + var request = phProvider._getParameterHint(); + request.fail(function (status) { + requestStatus = status; + }); + + waitsForFail(request, "ParameterHints"); + }); + + runs(function () { + expect(requestStatus).toBe(null); + }); } /** * Verify the parameter hint is not visible. */ function expectParameterHintClosed() { - expect(ParameterHintManager.isHintDisplayed()).toBe(false); - } - - /* - * Wait for a hint response object to resolve, then apply a callback - * to the result - * - * @param {Object + jQuery.Deferred} hintObj - a hint response object, - * possibly deferred - * @param {Function} callback - the callback to apply to the resolved - * hint response object - */ - function _waitForParameterHint(hintObj, callback) { - var complete = false, - hint = null; - - hintObj.done(function () { - hint = JSCodeHints.getSession().getParameterHint(); - complete = true; - }); - - waitsFor(function () { - return complete; - }, "Expected parameter hint did not resolve", 3000); - - runs(function () { callback(hint); }); + expect(phProvider.isHintDisplayed()).toBe(false); } /** @@ -386,12 +374,9 @@ define(function (require, exports, module) { * @param {number} expectedParameter - the parameter at cursor. */ function expectParameterHint(expectedParams, expectedParameter) { - var request = ParameterHintManager.popUpHint(); - if (expectedParams === null) { - expect(request).toBe(null); - return; - } - + var requestHints = undefined, + request = null; + function expectHint(hint) { var params = hint.parameters, n = params.length, @@ -413,11 +398,29 @@ define(function (require, exports, module) { } } + + runs(function () { + request = phProvider._getParameterHint(); + + if (expectedParams === null) { + request.fail(function (result) { + requestHints = result; + }); + + waitsForFail(request, "ParameterHints"); + } else { + request.done(function (result) { + requestHints = result; + }); + + waitsForDone(request, "ParameterHints"); + } + }); - if (request) { - _waitForParameterHint(request, expectHint); + if (expectedParams === null) { + expect(requestHints).toBe(null); } else { - expectHint(JSCodeHints.getSession().getParameterHint()); + expectHint(requestHints); } } diff --git a/src/extensions/default/JavaScriptQuickEdit/unittests.js b/src/extensions/default/JavaScriptQuickEdit/unittests.js index 5302abca973..a452d5b0fb2 100644 --- a/src/extensions/default/JavaScriptQuickEdit/unittests.js +++ b/src/extensions/default/JavaScriptQuickEdit/unittests.js @@ -21,7 +21,7 @@ * */ -/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone */ +/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, waitsForFail */ define(function (require, exports, module) { "use strict"; @@ -275,7 +275,7 @@ define(function (require, exports, module) { describe("Code hints tests within quick edit window ", function () { var JSCodeHints, - ParameterHintManager; + ParameterHintProvider; /* * Ask provider for hints at current cursor position; expect it to @@ -345,31 +345,6 @@ define(function (require, exports, module) { }); } - /* - * Wait for a hint response object to resolve, then apply a callback - * to the result - * - * @param {Object + jQuery.Deferred} hintObj - a hint response object, - * possibly deferred - * @param {Function} callback - the callback to apply to the resolved - * hint response object - */ - function _waitForParameterHint(hintObj, callback) { - var complete = false, - hint = null; - - hintObj.done(function () { - hint = JSCodeHints.getSession().getParameterHint(); - complete = true; - }); - - waitsFor(function () { - return complete; - }, "Expected parameter hint did not resolve", 3000); - - runs(function () { callback(hint); }); - } - /** * Show a function hint based on the code at the cursor. Verify the * hint matches the passed in value. @@ -380,11 +355,8 @@ define(function (require, exports, module) { * @param {number} expectedParameter - the parameter at cursor. */ function expectParameterHint(expectedParams, expectedParameter) { - var request = ParameterHintManager.popUpHint(); - if (expectedParams === null) { - expect(request).toBe(null); - return; - } + var requestHints = undefined, + request = null; function expectHint(hint) { var params = hint.parameters, @@ -408,10 +380,28 @@ define(function (require, exports, module) { } - if (request) { - _waitForParameterHint(request, expectHint); + runs(function () { + request = ParameterHintProvider._getParameterHint(); + + if (expectedParams === null) { + request.fail(function (result) { + requestHints = result; + }); + + waitsForFail(request, "ParameterHints"); + } else { + request.done(function (result) { + requestHints = result; + }); + + waitsForDone(request, "ParameterHints"); + } + }); + + if (expectedParams === null) { + expect(requestHints).toBe(null); } else { - expectHint(JSCodeHints.getSession().getParameterHint()); + expectHint(requestHints); } } @@ -462,7 +452,7 @@ define(function (require, exports, module) { var extensionRequire = testWindow.brackets.getModule("utils/ExtensionLoader"). getRequireContextForExtension("JavaScriptCodeHints"); JSCodeHints = extensionRequire("main"); - ParameterHintManager = extensionRequire("ParameterHintManager"); + ParameterHintProvider = extensionRequire("ParameterHintsProvider").JSParameterHintsProvider(); } beforeEach(function () { @@ -472,7 +462,7 @@ define(function (require, exports, module) { afterEach(function () { JSCodeHints = null; - ParameterHintManager = null; + ParameterHintProvider = null; }); it("should see code hint lists in quick editor", function () { diff --git a/src/features/JumpToDefManager.js b/src/features/JumpToDefManager.js new file mode 100644 index 00000000000..570c16fe3a2 --- /dev/null +++ b/src/features/JumpToDefManager.js @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var Commands = require("command/Commands"), + Strings = require("strings"), + AppInit = require("utils/AppInit"), + CommandManager = require("command/CommandManager"), + EditorManager = require("editor/EditorManager"), + ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler; + + var _providerRegistrationHandler = new ProviderRegistrationHandler(), + registerJumpToDefProvider = _providerRegistrationHandler.registerProvider.bind(_providerRegistrationHandler), + removeJumpToDefProvider = _providerRegistrationHandler.removeProvider.bind(_providerRegistrationHandler); + + + /** + * Asynchronously asks providers to handle jump-to-definition. + * @return {!Promise} Resolved when the provider signals that it's done; rejected if no + * provider responded or the provider that responded failed. + */ + function _doJumpToDef() { + var request = null, + result = new $.Deferred(), + jumpToDefProvider = null, + editor = EditorManager.getActiveEditor(); + + if (editor) { + // Find a suitable provider, if any + var language = editor.getLanguageForSelection(), + enabledProviders = _providerRegistrationHandler.getProvidersForLanguageId(language.getId()); + + + enabledProviders.some(function (item, index) { + if (item.provider.canJumpToDef(editor)) { + jumpToDefProvider = item.provider; + return true; + } + }); + + if (jumpToDefProvider) { + request = jumpToDefProvider.doJumpToDef(editor); + + if (request) { + request.done(function () { + result.resolve(); + }).fail(function () { + result.reject(); + }); + } else { + result.reject(); + } + } else { + result.reject(); + } + } else { + result.reject(); + } + + return result.promise(); + } + + CommandManager.register(Strings.CMD_JUMPTO_DEFINITION, Commands.NAVIGATE_JUMPTO_DEFINITION, _doJumpToDef); + + exports.registerJumpToDefProvider = registerJumpToDefProvider; + exports.removeJumpToDefProvider = removeJumpToDefProvider; +}); diff --git a/src/features/ParameterHintsManager.js b/src/features/ParameterHintsManager.js new file mode 100644 index 00000000000..adf2b5c5352 --- /dev/null +++ b/src/features/ParameterHintsManager.js @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var _ = require("thirdparty/lodash"); + + var Commands = require("command/Commands"), + AppInit = require("utils/AppInit"), + CommandManager = require("command/CommandManager"), + EditorManager = require("editor/EditorManager"), + Menus = require("command/Menus"), + KeyEvent = require("utils/KeyEvent"), + Strings = require("strings"), + ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler; + + + /** @const {string} Show Function Hint command ID */ + var SHOW_PARAMETER_HINT_CMD_ID = "showParameterHint", // string must MATCH string in native code (brackets_extensions) + hintContainerHTML = require("text!htmlContent/parameter-hint-template.html"), + KeyboardPrefs = { + "showParameterHint": [ + { + "key": "Ctrl-Shift-Space" + }, + { + "key": "Ctrl-Shift-Space", + "platform": "mac" + } + ] + }; + + var $hintContainer, // function hint container + $hintContent, // function hint content holder + hintState = {}, + lastChar = null, + sessionEditor = null, + keyDownEditor = null; + + // Constants + var POINTER_TOP_OFFSET = 4, // Size of margin + border of hint. + POSITION_BELOW_OFFSET = 4; // Amount to adjust to top position when the preview bubble is below the text + + // keep jslint from complaining about handleCursorActivity being used before + // it was defined. + var handleCursorActivity; + + var _providerRegistrationHandler = new ProviderRegistrationHandler(), + registerHintProvider = _providerRegistrationHandler.registerProvider.bind(_providerRegistrationHandler), + removeHintProvider = _providerRegistrationHandler.removeProvider.bind(_providerRegistrationHandler); + + /** + * Position a function hint. + * + * @param {number} xpos + * @param {number} ypos + * @param {number} ybot + */ + function positionHint(xpos, ypos, ybot) { + var hintWidth = $hintContainer.width(), + hintHeight = $hintContainer.height(), + top = ypos - hintHeight - POINTER_TOP_OFFSET, + left = xpos, + $editorHolder = $("#editor-holder"), + editorLeft; + + if ($editorHolder.offset() === undefined) { + // this happens in jasmine tests that run + // without a windowed document. + return; + } + + editorLeft = $editorHolder.offset().left; + left = Math.max(left, editorLeft); + left = Math.min(left, editorLeft + $editorHolder.width() - hintWidth); + + if (top < 0) { + $hintContainer.removeClass("preview-bubble-above"); + $hintContainer.addClass("preview-bubble-below"); + top = ybot + POSITION_BELOW_OFFSET; + $hintContainer.offset({ + left: left, + top: top + }); + } else { + $hintContainer.removeClass("preview-bubble-below"); + $hintContainer.addClass("preview-bubble-above"); + $hintContainer.offset({ + left: left, + top: top - POINTER_TOP_OFFSET + }); + } + } + + /** + * Format the given parameter array. Handles separators between + * parameters, syntax for optional parameters, and the order of the + * parameter type and parameter name. + * + * @param {!Array.<{name: string, type: string, isOptional: boolean}>} params - + * array of parameter descriptors + * @param {function(string)=} appendSeparators - callback function to append separators. + * The separator is passed to the callback. + * @param {function(string, number)=} appendParameter - callback function to append parameter. + * The formatted parameter type and name is passed to the callback along with the + * current index of the parameter. + * @param {boolean=} typesOnly - only show parameter types. The + * default behavior is to include both parameter names and types. + * @return {string} - formatted parameter hint + */ + function _formatParameterHint(params, appendSeparators, appendParameter, typesOnly) { + var result = "", + pendingOptional = false; + + appendParameter("(", "", -1); + params.forEach(function (value, i) { + var param = value.label || value.type, + documentation = value.documentation, + separators = ""; + + if (value.isOptional) { + // if an optional param is following by an optional parameter, then + // terminate the bracket. Otherwise enclose a required parameter + // in the same bracket. + if (pendingOptional) { + separators += "]"; + } + + pendingOptional = true; + } + + if (i > 0) { + separators += ", "; + } + + if (value.isOptional) { + separators += "["; + } + + if (appendSeparators) { + appendSeparators(separators); + } + + result += separators; + + if (!typesOnly && value.name) { + param += " " + value.name; + } + + if (appendParameter) { + appendParameter(param, documentation, i); + } + + result += param; + + }); + + if (pendingOptional) { + if (appendSeparators) { + appendSeparators("]"); + } + + result += "]"; + } + appendParameter(")", "", -1); + + return result; + } + + /** + * Bold the parameter at the caret. + * + * @param {{inFunctionCall: boolean, functionCallPos: {line: number, ch: number}}} functionInfo - + * tells if the caret is in a function call and the position + * of the function call. + */ + function formatHint(hints) { + $hintContent.empty(); + $hintContent.addClass("brackets-hints"); + + function appendSeparators(separators) { + $hintContent.append(separators); + } + + function appendParameter(param, documentation, index) { + if (hints.currentIndex === index) { + $hintContent.append($("") + .append(_.escape(param)) + .addClass("current-parameter")); + } else { + $hintContent.append($("") + .append(_.escape(param)) + .addClass("parameter")); + } + } + + if (hints.parameters.length > 0) { + _formatParameterHint(hints.parameters, appendSeparators, appendParameter); + } else { + $hintContent.append(_.escape(Strings.NO_ARGUMENTS)); + } + } + + /** + * Dismiss the function hint. + * + */ + function dismissHint(editor) { + if (hintState.visible) { + $hintContainer.hide(); + $hintContent.empty(); + hintState = {}; + + if (editor) { + editor.off("cursorActivity.ParameterHinting", handleCursorActivity); + sessionEditor = null; + } else if (sessionEditor) { + sessionEditor.off("cursorActivity.ParameterHinting", handleCursorActivity); + sessionEditor = null; + } + } + } + + /** + * Pop up a function hint on the line above the caret position. + * + * @param {object=} editor - current Active Editor + * @param {boolean} True if hints are invoked through cursor activity. + * @return {jQuery.Promise} - The promise will not complete until the + * hint has completed. Returns null, if the function hint is already + * displayed or there is no function hint at the cursor. + * + */ + function popUpHint(editor, explicit, onCursorActivity) { + var request = null; + var $deferredPopUp = $.Deferred(); + var sessionProvider = null; + + dismissHint(editor); + // Find a suitable provider, if any + var language = editor.getLanguageForSelection(), + enabledProviders = _providerRegistrationHandler.getProvidersForLanguageId(language.getId()); + + enabledProviders.some(function (item, index) { + if (item.provider.hasParameterHints(editor, lastChar)) { + sessionProvider = item.provider; + return true; + } + }); + + if (sessionProvider) { + request = sessionProvider.getParameterHints(explicit, onCursorActivity); + } + + if (request) { + request.done(function (parameterHint) { + var cm = editor._codeMirror, + pos = parameterHint.functionCallPos || editor.getCursorPos(); + + pos = cm.charCoords(pos); + formatHint(parameterHint); + + $hintContainer.show(); + positionHint(pos.left, pos.top, pos.bottom); + hintState.visible = true; + + sessionEditor = editor; + editor.on("cursorActivity.ParameterHinting", handleCursorActivity); + $deferredPopUp.resolveWith(null); + }).fail(function () { + hintState = {}; + }); + } + + return $deferredPopUp; + } + + /** + * Show the parameter the cursor is on in bold when the cursor moves. + * Dismiss the pop up when the cursor moves off the function. + */ + handleCursorActivity = function (event, editor) { + if (editor) { + popUpHint(editor, false, true); + } else { + dismissHint(); + } + }; + + /** + * Install function hint listeners. + * + * @param {Editor} editor - editor context on which to listen for + * changes + */ + function installListeners(editor) { + editor.on("keydown.ParameterHinting", function (event, editor, domEvent) { + if (domEvent.keyCode === KeyEvent.DOM_VK_ESCAPE) { + dismissHint(editor); + } + }).on("scroll.ParameterHinting", function () { + dismissHint(editor); + }) + .on("editorChange.ParameterHinting", _handleChange) + .on("keypress.ParameterHinting", _handleKeypressEvent); + } + + /** + * Clean up after installListeners() + * @param {!Editor} editor + */ + function uninstallListeners(editor) { + editor.off(".ParameterHinting"); + } + + function _handleKeypressEvent(jqEvent, editor, event) { + keyDownEditor = editor; + // Last inserted character, used later by handleChange + lastChar = String.fromCharCode(event.charCode); + } + + /** + * Start a new implicit hinting session, or update the existing hint list. + * Called by the editor after handleKeyEvent, which is responsible for setting + * the lastChar. + * + * @param {Event} event + * @param {Editor} editor + * @param {{from: Pos, to: Pos, text: Array, origin: string}} changeList + */ + function _handleChange(event, editor, changeList) { + if (lastChar && (lastChar === '(' || lastChar === ',') && editor === keyDownEditor) { + keyDownEditor = null; + popUpHint(editor); + } + } + + function activeEditorChangeHandler(event, current, previous) { + + if (previous) { + //Removing all old Handlers + previous.document + .off("languageChanged.ParameterHinting"); + uninstallListeners(previous); + } + + if (current) { + current.document + .on("languageChanged.ParameterHinting", function () { + // If current doc's language changed, reset our state by treating it as if the user switched to a + // different document altogether + uninstallListeners(current); + installListeners(current); + }); + installListeners(current); + } + } + + /** + * Show a parameter hint in its own pop-up. + * + */ + function handleShowParameterHint() { + var editor = EditorManager.getActiveEditor(); + // Pop up function hint + popUpHint(editor, true, false); + } + + AppInit.appReady(function () { + CommandManager.register(Strings.CMD_SHOW_PARAMETER_HINT, SHOW_PARAMETER_HINT_CMD_ID, handleShowParameterHint); + + // Add the menu items + var menu = Menus.getMenu(Menus.AppMenuBar.EDIT_MENU); + if (menu) { + menu.addMenuItem(SHOW_PARAMETER_HINT_CMD_ID, KeyboardPrefs.showParameterHint, Menus.AFTER, Commands.SHOW_CODE_HINTS); + } + // Create the function hint container + $hintContainer = $(hintContainerHTML).appendTo($("body")); + $hintContent = $hintContainer.find(".function-hint-content-new"); + activeEditorChangeHandler(null, EditorManager.getActiveEditor(), null); + + EditorManager.on("activeEditorChange", activeEditorChangeHandler); + + CommandManager.on("beforeExecuteCommand", function (event, commandId) { + if (commandId !== SHOW_PARAMETER_HINT_CMD_ID && + commandId !== Commands.SHOW_CODE_HINTS) { + dismissHint(); + } + }); + }); + + exports.registerHintProvider = registerHintProvider; + exports.removeHintProvider = removeHintProvider; +}); diff --git a/src/features/PriorityBasedRegistration.js b/src/features/PriorityBasedRegistration.js new file mode 100644 index 00000000000..1ad6214d48e --- /dev/null +++ b/src/features/PriorityBasedRegistration.js @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint-disable indent */ +define(function (require, exports, module) { + "use strict"; + + var PreferencesManager = require("preferences/PreferencesManager"); + + /** + * Comparator to sort providers from high to low priority + */ + function _providerSort(a, b) { + return b.priority - a.priority; + } + + + function RegistrationHandler() { + this._providers = { + "all": [] + }; + } + + /** + * The method by which a Provider registers its willingness to + * providing tooling feature for editors in a given language. + * + * @param {!Provider} provider + * The provider to be registered, described below. + * + * @param {!Array.} languageIds + * The set of language ids for which the provider is capable of + * providing tooling feature. If the special language id name "all" is included then + * the provider may be called for any language. + * + * @param {?number} priority + * Used to break ties among providers for a particular language. + * Providers with a higher number will be asked for tooling before those + * with a lower priority value. Defaults to zero. + */ + RegistrationHandler.prototype.registerProvider = function (providerInfo, languageIds, priority) { + var providerObj = { + provider: providerInfo, + priority: priority || 0 + }, + self = this; + + if (languageIds.indexOf("all") !== -1) { + // Ignore anything else in languageIds and just register for every language. This includes + // the special "all" language since its key is in the hintProviders map from the beginning. + var languageId; + for (languageId in self._providers) { + if (self._providers.hasOwnProperty(languageId)) { + self._providers[languageId].push(providerObj); + self._providers[languageId].sort(_providerSort); + } + } + } else { + languageIds.forEach(function (languageId) { + if (!self._providers[languageId]) { + // Initialize provider list with any existing all-language providers + self._providers[languageId] = Array.prototype.concat(self._providers.all); + } + self._providers[languageId].push(providerObj); + self._providers[languageId].sort(_providerSort); + }); + } + }; + + /** + * Remove a code hint provider + * @param {!CodeHintProvider} provider Code hint provider to remove + * @param {(string|Array.)=} targetLanguageId Optional set of + * language IDs for languages to remove the provider for. Defaults + * to all languages. + */ + RegistrationHandler.prototype.removeProvider = function (provider, targetLanguageId) { + var index, + providers, + targetLanguageIdArr, + self = this; + + if (Array.isArray(targetLanguageId)) { + targetLanguageIdArr = targetLanguageId; + } else if (targetLanguageId) { + targetLanguageIdArr = [targetLanguageId]; + } else { + targetLanguageIdArr = Object.keys(self._providers); + } + + targetLanguageIdArr.forEach(function (languageId) { + providers = self._providers[languageId]; + + for (index = 0; index < providers.length; index++) { + if (providers[index].provider === provider) { + providers.splice(index, 1); + break; + } + } + }); + }; + + + RegistrationHandler.prototype.getProvidersForLanguageId = function (languageId) { + var providers = this._providers[languageId] || this._providers.all; + + // Exclude providers that are explicitly disabled in the preferences. + // All providers that do not have their constructor + // names listed in the preferences are enabled by default. + return providers.filter(function (provider) { + var prefKey = "tooling." + provider.provider.constructor.name; + return PreferencesManager.get(prefKey) !== false; + }); + }; + + + exports.RegistrationHandler = RegistrationHandler; +}); diff --git a/src/htmlContent/parameter-hint-template.html b/src/htmlContent/parameter-hint-template.html new file mode 100644 index 00000000000..ffdd53d620b --- /dev/null +++ b/src/htmlContent/parameter-hint-template.html @@ -0,0 +1,4 @@ +
+
+
+
diff --git a/src/languageTools/BracketsToNodeInterface.js b/src/languageTools/BracketsToNodeInterface.js new file mode 100644 index 00000000000..71c12970218 --- /dev/null +++ b/src/languageTools/BracketsToNodeInterface.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-invalid-this: 0*/ +define(function (require, exports, module) { + "use strict"; + + function BracketsToNodeInterface(domain) { + this.domain = domain; + this.bracketsFn = {}; + + this._registerDataEvent(); + } + + BracketsToNodeInterface.prototype._messageHandler = function (evt, params) { + var methodName = params.method, + self = this; + + function _getErrorString(err) { + if (typeof err === "string") { + return err; + } else if (err && err.name && err.name === "Error") { + return err.message; + } + return "Error in executing " + methodName; + + } + + function _sendResponse(response) { + var responseParams = { + requestId: params.requestId, + params: response + }; + self.domain.exec("response", responseParams); + } + + function _sendError(err) { + var responseParams = { + requestId: params.requestId, + error: _getErrorString(err) + }; + self.domain.exec("response", responseParams); + } + + if (self.bracketsFn[methodName]) { + var method = self.bracketsFn[methodName]; + try { + var response = method.call(null, params.params); + if (params.respond && params.requestId) { + if (response.promise) { + response.done(function (result) { + _sendResponse(result); + }).fail(function (err) { + _sendError(err); + }); + } else { + _sendResponse(response); + } + } + } catch (err) { + if (params.respond && params.requestId) { + _sendError(err); + } + } + } + + }; + + + BracketsToNodeInterface.prototype._registerDataEvent = function () { + this.domain.on("data", this._messageHandler.bind(this)); + }; + + BracketsToNodeInterface.prototype.createInterface = function (methodName, isAsync) { + var self = this; + return function (params) { + var execEvent = isAsync ? "asyncData" : "data"; + var callObject = { + method: methodName, + params: params + }; + return self.domain.exec(execEvent, callObject); + }; + }; + + BracketsToNodeInterface.prototype.registerMethod = function (methodName, methodHandle) { + if (methodName && methodHandle && + typeof methodName === "string" && typeof methodHandle === "function") { + this.bracketsFn[methodName] = methodHandle; + } + }; + + BracketsToNodeInterface.prototype.registerMethods = function (methodList) { + var self = this; + methodList.forEach(function (methodObj) { + self.registerMethod(methodObj.methodName, methodObj.methodHandle); + }); + }; + + exports.BracketsToNodeInterface = BracketsToNodeInterface; +}); diff --git a/src/languageTools/ClientLoader.js b/src/languageTools/ClientLoader.js new file mode 100644 index 00000000000..c2fa615da6b --- /dev/null +++ b/src/languageTools/ClientLoader.js @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-console: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var ToolingInfo = JSON.parse(require("text!languageTools/ToolingInfo.json")), + NodeDomain = require("utils/NodeDomain"), + FileUtils = require("file/FileUtils"), + BracketsToNodeInterface = require("languageTools/BracketsToNodeInterface").BracketsToNodeInterface; + + //Register paths required for Language Client and also register default brackets capabilities. + var _bracketsPath = FileUtils.getNativeBracketsDirectoryPath(); + // The native directory path ends with either "test" or "src". + _bracketsPath = _bracketsPath.replace(/\/test$/, "/src"); // convert from "test" to "src" + + var _modulePath = FileUtils.getNativeModuleDirectoryPath(module), + _nodePath = "node/RegisterLanguageClientInfo", + _domainPath = [_bracketsPath, _modulePath, _nodePath].join("/"), + clientInfoDomain = new NodeDomain("LanguageClientInfo", _domainPath), + //Init node with Information required by Language Client + clientInfoLoadedPromise = clientInfoDomain.exec("initialize", _bracketsPath, ToolingInfo), + //Clients that have to be loaded once the LanguageClient info is successfully loaded on the + //node side. + pendingClientsToBeLoaded = []; + + //Attach success and failure function for the clientInfoLoadedPromise + clientInfoLoadedPromise.then(function () { + pendingClientsToBeLoaded.forEach(function (pendingClient) { + pendingClient.load(); + }); + }, function () { + console.log("Failed to Initialize LanguageClient Module Information."); + }); + + function syncPrefsWithDomain(languageToolsPrefs) { + if (clientInfoDomain) { + clientInfoDomain.exec("syncPreferences", languageToolsPrefs); + } + } + + function _createNodeDomain(domainName, domainPath) { + return new NodeDomain(domainName, domainPath); + } + + function loadLanguageClientDomain(clientName, domainPath) { + //generate a random hash name for the domain, this is the client id + var domainName = clientName, + result = $.Deferred(), + languageClientDomain = _createNodeDomain(domainName, domainPath); + + if (languageClientDomain) { + languageClientDomain.promise() + .done(function () { + console.log(domainPath + " domain successfully created"); + result.resolve(languageClientDomain); + }) + .fail(function (err) { + console.error(domainPath + " domain could not be created."); + result.reject(); + }); + } else { + console.error(domainPath + " domain could not be created."); + result.reject(); + } + + return result; + } + + function createNodeInterfaceForDomain(languageClientDomain) { + var nodeInterface = new BracketsToNodeInterface(languageClientDomain); + + return nodeInterface; + } + + function _clientLoader(clientName, clientFilePath, clientPromise) { + loadLanguageClientDomain(clientName, clientFilePath) + .then(function (languageClientDomain) { + var languageClientInterface = createNodeInterfaceForDomain(languageClientDomain); + + clientPromise.resolve({ + name: clientName, + interface: languageClientInterface + }); + }, clientPromise.reject); + } + + function initiateLanguageClient(clientName, clientFilePath) { + var result = $.Deferred(); + + //Only load clients after the LanguageClient Info has been initialized + if (clientInfoLoadedPromise.state() === "pending") { + var pendingClient = { + load: _clientLoader.bind(null, clientName, clientFilePath, result) + }; + pendingClientsToBeLoaded.push(pendingClient); + } else { + _clientLoader(clientName, clientFilePath, result); + } + + return result; + } + + exports.initiateLanguageClient = initiateLanguageClient; + exports.syncPrefsWithDomain = syncPrefsWithDomain; +}); diff --git a/src/languageTools/DefaultEventHandlers.js b/src/languageTools/DefaultEventHandlers.js new file mode 100644 index 00000000000..e544ae1b557 --- /dev/null +++ b/src/languageTools/DefaultEventHandlers.js @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint-disable indent */ +/* eslint no-console: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageManager = require("language/LanguageManager"), + ProjectManager = require("project/ProjectManager"), + PathConverters = require("languageTools/PathConverters"); + + function EventPropagationProvider(client) { + this.client = client; + this.previousProject = ""; + this.currentProject = ProjectManager.getProjectRoot(); + } + + EventPropagationProvider.prototype._sendDocumentOpenNotification = function (languageId, doc) { + if (!this.client) { + return; + } + + if (this.client._languages.includes(languageId)) { + this.client.notifyTextDocumentOpened({ + languageId: languageId, + filePath: (doc.file._path || doc.file.fullPath), + fileContent: doc.getText() + }); + } + }; + + EventPropagationProvider.prototype.handleActiveEditorChange = function (event, current, previous) { + var self = this; + + if (!this.client) { + return; + } + + if (previous) { + previous.document + .off("languageChanged.language-tools"); + var previousLanguageId = LanguageManager.getLanguageForPath(previous.document.file.fullPath).getId(); + if (this.client._languages.includes(previousLanguageId)) { + this.client.notifyTextDocumentClosed({ + filePath: (previous.document.file._path || previous.document.file.fullPath) + }); + } + } + if (current) { + var currentLanguageId = LanguageManager.getLanguageForPath(current.document.file.fullPath).getId(); + current.document + .on("languageChanged.language-tools", function () { + var languageId = LanguageManager.getLanguageForPath(current.document.file.fullPath).getId(); + self._sendDocumentOpenNotification(languageId, current.document); + }); + self._sendDocumentOpenNotification(currentLanguageId, current.document); + } + }; + + EventPropagationProvider.prototype.handleProjectOpen = function (event, directory) { + if (!this.client) { + return; + } + + this.currentProject = directory.fullPath; + + this.client.notifyProjectRootsChanged({ + foldersAdded: [this.currentProject], + foldersRemoved: [this.previousProject] + }); + }; + + EventPropagationProvider.prototype.handleProjectClose = function (event, directory) { + if (!this.client) { + return; + } + + this.previousProject = directory.fullPath; + }; + + EventPropagationProvider.prototype.handleDocumentDirty = function (event, doc) { + if (!this.client) { + return; + } + + if (!doc.isDirty) { + var docLanguageId = LanguageManager.getLanguageForPath(doc.file.fullPath).getId(); + if (this.client._languages.includes(docLanguageId)) { + this.client.notifyTextDocumentSave({ + filePath: (doc.file._path || doc.file.fullPath) + }); + } + } + }; + + EventPropagationProvider.prototype.handleDocumentChange = function (event, doc, changeList) { + if (!this.client) { + return; + } + + var docLanguageId = LanguageManager.getLanguageForPath(doc.file.fullPath).getId(); + if (this.client._languages.includes(docLanguageId)) { + this.client.notifyTextDocumentChanged({ + filePath: (doc.file._path || doc.file.fullPath), + fileContent: doc.getText() + }); + } + }; + + EventPropagationProvider.prototype.handleDocumentRename = function (event, oldName, newName) { + if (!this.client) { + return; + } + + var oldDocLanguageId = LanguageManager.getLanguageForPath(oldName).getId(); + if (this.client._languages.includes(oldDocLanguageId)) { + this.client.notifyTextDocumentClosed({ + filePath: oldName + }); + } + + var newDocLanguageId = LanguageManager.getLanguageForPath(newName).getId(); + if (this.client._languages.includes(newDocLanguageId)) { + this.client.notifyTextDocumentOpened({ + filePath: newName + }); + } + }; + + EventPropagationProvider.prototype.handleAppClose = function (event) { + //Also handles Reload with Extensions + if (!this.client) { + return; + } + + this.client.stop(); + }; + + function handleProjectFoldersRequest(event) { + var projectRoot = ProjectManager.getProjectRoot(), + workspaceFolders = [projectRoot]; + + workspaceFolders = PathConverters.convertToWorkspaceFolders(workspaceFolders); + + return $.Deferred().resolve(workspaceFolders); + }; + + EventPropagationProvider.prototype.registerClientForEditorEvent = function () { + if (this.client) { + var handleActiveEditorChange = this.handleActiveEditorChange.bind(this), + handleProjectOpen = this.handleProjectOpen.bind(this), + handleProjectClose = this.handleProjectClose.bind(this), + handleDocumentDirty = this.handleDocumentDirty.bind(this), + handleDocumentChange = this.handleDocumentChange.bind(this), + handleDocumentRename = this.handleDocumentRename.bind(this), + handleAppClose = this.handleAppClose.bind(this); + + this.client.addOnEditorChangeHandler(handleActiveEditorChange); + this.client.addOnProjectOpenHandler(handleProjectOpen); + this.client.addBeforeProjectCloseHandler(handleProjectClose); + this.client.addOnDocumentDirtyFlagChangeHandler(handleDocumentDirty); + this.client.addOnDocumentChangeHandler(handleDocumentChange); + this.client.addOnFileRenameHandler(handleDocumentRename); + this.client.addBeforeAppClose(handleAppClose); + this.client.onProjectFoldersRequest(handleProjectFoldersRequest); + } else { + console.log("No client provided for event propagation"); + } + }; + + exports.EventPropagationProvider = EventPropagationProvider; +}); diff --git a/src/languageTools/DefaultProviders.js b/src/languageTools/DefaultProviders.js new file mode 100644 index 00000000000..d5cc5ad8a9b --- /dev/null +++ b/src/languageTools/DefaultProviders.js @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global Map*/ +/* eslint-disable indent */ +/* eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var _ = brackets.getModule("thirdparty/lodash"); + + var EditorManager = require('editor/EditorManager'), + ExtensionUtils = require("utils/ExtensionUtils"), + CommandManager = require("command/CommandManager"), + Commands = require("command/Commands"), + TokenUtils = require("utils/TokenUtils"), + StringMatch = require("utils/StringMatch"), + CodeInspection = require("language/CodeInspection"), + PathConverters = require("languageTools/PathConverters"), + matcher = new StringMatch.StringMatcher({ + preferPrefixMatches: true + }); + + ExtensionUtils.loadStyleSheet(module, "styles/default_provider_style.css"); + + function CodeHintsProvider(client) { + this.client = client; + this.query = ""; + } + + function formatTypeDataForToken($hintObj, token) { + $hintObj.addClass('brackets-hints-with-type-details'); + if (token.detail) { + if (token.detail.trim() !== '?') { + if (token.detail.length < 30) { + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("brackets-hints-type-details"); + } + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("hint-description"); + } + } else { + if (token.keyword) { + $('keyword').appendTo($hintObj).addClass("brackets-hints-keyword"); + } + } + if (token.documentation) { + $hintObj.attr('title', token.documentation); + $('').text(token.documentation.trim()).appendTo($hintObj).addClass("hint-doc"); + } + } + + function filterWithQueryAndMatcher(hints, query) { + var matchResults = $.map(hints, function (hint) { + var searchResult = matcher.match(hint.label, query); + if (searchResult) { + for (var key in hint) { + searchResult[key] = hint[key]; + } + } + + return searchResult; + }); + + return matchResults; + } + + CodeHintsProvider.prototype.hasHints = function (editor, implicitChar) { + if (!this.client) { + return false; + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.completionProvider) { + return false; + } + + return true; + }; + + CodeHintsProvider.prototype.getHints = function (implicitChar) { + if (!this.client) { + return null; + } + + var editor = EditorManager.getActiveEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + $deferredHints = $.Deferred(), + self = this; + + this.client.requestHints({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + var context = TokenUtils.getInitialContext(editor._codeMirror, pos), + hints = []; + + self.query = context.token.string.slice(0, context.pos.ch - context.token.start); + if (msgObj) { + var res = msgObj.items, + filteredHints = filterWithQueryAndMatcher(res, self.query); + + StringMatch.basicMatchSort(filteredHints); + filteredHints.forEach(function (element) { + var $fHint = $("") + .addClass("brackets-hints"); + + if (element.stringRanges) { + element.stringRanges.forEach(function (item) { + if (item.matched) { + $fHint.append($("") + .append(_.escape(item.text)) + .addClass("matched-hint")); + } else { + $fHint.append(_.escape(item.text)); + } + }); + } else { + $fHint.text(element.label); + } + + $fHint.data("token", element); + formatTypeDataForToken($fHint, element); + hints.push($fHint); + }); + } + + $deferredHints.resolve({ + "hints": hints + }); + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + CodeHintsProvider.prototype.insertHint = function ($hint) { + var editor = EditorManager.getActiveEditor(), + cursor = editor.getCursorPos(), + token = $hint.data("token"), + txt = null, + query = this.query, + start = { + line: cursor.line, + ch: cursor.ch - query.length + }, + + end = { + line: cursor.line, + ch: cursor.ch + }; + + txt = token.label; + if (token.textEdit && token.textEdit.newText) { + txt = token.textEdit.newText; + start = { + line: token.textEdit.range.start.line, + ch: token.textEdit.range.start.character + }; + end = { + line: token.textEdit.range.end.line, + ch: token.textEdit.range.end.character + }; + } + + if (editor) { + editor.document.replaceRange(txt, start, end); + } + // Return false to indicate that another hinting session is not needed + return false; + }; + + function ParameterHintsProvider(client) { + this.client = client; + } + + ParameterHintsProvider.prototype.hasParameterHints = function (editor, implicitChar) { + if (!this.client) { + return false; + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.signatureHelpProvider) { + return false; + } + + return true; + }; + + ParameterHintsProvider.prototype.getParameterHints = function () { + if (!this.client) { + return null; + } + + var editor = EditorManager.getActiveEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + $deferredHints = $.Deferred(); + + this.client.requestParameterHints({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + let paramList = []; + let label; + let activeParameter; + if (msgObj) { + let res; + res = msgObj.signatures; + activeParameter = msgObj.activeParameter; + if (res && res.length) { + res.forEach(function (element) { + label = element.documentation; + let param = element.parameters; + param.forEach(ele => { + paramList.push({ + label: ele.label, + documentation: ele.documentation + }); + }); + }); + + $deferredHints.resolve({ + parameters: paramList, + currentIndex: activeParameter, + functionDocumentation: label + }); + } else { + $deferredHints.reject(); + } + } else { + $deferredHints.reject(); + } + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + /** + * Utility function to make the jump + * @param {Object} curPos - target postion for the cursor after the jump + */ + function setJumpPosition(curPos) { + EditorManager.getCurrentFullEditor().setCursorPos(curPos.line, curPos.ch, true); + } + + function JumpToDefProvider(client) { + this.client = client; + } + + JumpToDefProvider.prototype.canJumpToDef = function (editor, implicitChar) { + if (!this.client) { + return false; + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.definitionProvider) { + return false; + } + + return true; + }; + + /** + * Method to handle jump to definition feature. + */ + JumpToDefProvider.prototype.doJumpToDef = function () { + if (!this.client) { + return null; + } + + var editor = EditorManager.getFocusedEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + docPathUri = PathConverters.pathToUri(docPath), + $deferredHints = $.Deferred(); + + this.client.gotoDefinition({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + //For Older servers + if (Array.isArray(msgObj)) { + msgObj = msgObj[msgObj.length - 1]; + } + + if (msgObj && msgObj.range) { + var docUri = msgObj.uri, + startCurPos = {}; + startCurPos.line = msgObj.range.start.line; + startCurPos.ch = msgObj.range.start.character; + + if (docUri !== docPathUri) { + let documentPath = PathConverters.uriToPath(docUri); + CommandManager.execute(Commands.FILE_OPEN, { + fullPath: documentPath + }) + .done(function () { + setJumpPosition(startCurPos); + $deferredHints.resolve(); + }); + } else { //definition is in current document + setJumpPosition(startCurPos); + $deferredHints.resolve(); + } + } + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + function LintingProvider() { + this._results = new Map(); + } + + LintingProvider.prototype.clearExistingResults = function (filePath) { + var filePathProvided = !!filePath; + + if (filePathProvided) { + this._results.delete(filePath); + } else { + //clear all results + this._results.clear(); + } + }; + + /** + * Publish the diagnostics information related to current document + * @param {Object} msgObj - json object containing information associated with 'textDocument/publishDiagnostics' notification from server + */ + LintingProvider.prototype.setInspectionResults = function (msgObj) { + let diagnostics = msgObj.diagnostics, + filePath = PathConverters.uriToPath(msgObj.uri), + errors = []; + + errors = diagnostics.map(function (obj) { + return { + pos: { + line: obj.range.start.line, + ch: obj.range.start.character + }, + message: obj.message, + type: (obj.severity === 1 ? CodeInspection.Type.ERROR : (obj.severity === 2 ? CodeInspection.Type.WARNING : CodeInspection.Type.META)) + }; + }); + + this._results.set(filePath, { + errors: errors + }); + }; + + LintingProvider.prototype.getInspectionResults = function (fileText, filePath) { + return this._results.get(filePath); + }; + + exports.CodeHintsProvider = CodeHintsProvider; + exports.ParameterHintsProvider = ParameterHintsProvider; + exports.JumpToDefProvider = JumpToDefProvider; + exports.LintingProvider = LintingProvider; +}); diff --git a/src/languageTools/LanguageClient/Connection.js b/src/languageTools/LanguageClient/Connection.js new file mode 100644 index 00000000000..47aa9c6747d --- /dev/null +++ b/src/languageTools/LanguageClient/Connection.js @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports */ +/*eslint no-console: 0*/ +/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ +(function () { + "use strict"; + + var protocol = require("vscode-languageserver-protocol"); + + var Actions = { + OnClose: { + Stop: 0, + Restart: 1 + }, + OnError: { + Ignore: 0, + Stop: 1 + } + }; + + function ActionController() { + this.restartsTimes = []; + } + + ActionController.prototype.getOnErrorAction = function (errorData) { + var errorCount = errorData[2]; + + if (errorCount <= 3) { + return Actions.OnError.Ignore; + } + + return Actions.OnError.Restart; + }; + + ActionController.prototype.getOnCloseAction = function () { + var currentTime = Date.now(); + this.restartsTimes.push(currentTime); + + var numRestarts = this.restartsTimes.length; + if (numRestarts < 5) { + return Actions.OnClose.Restart; + } + + var timeBetweenFiveRestarts = this.restartsTimes[numRestarts - 1] - this.restartsTimes[0]; + if (timeBetweenFiveRestarts <= 3 * 60 * 1000) { //3 minutes + return Actions.OnClose.Stop; + } + + this.restartsTimes.shift(); + return Actions.OnClose.Restart; + }; + + function _getOnCloseHandler(connection, actionController, restartLanguageClient) { + return function () { + try { + if (connection) { + connection.dispose(); + } + } catch (error) {} + + var action = Actions.OnClose.Stop; + try { + action = actionController.getOnCloseAction(); + } catch (error) {} + + + if (action === Actions.OnClose.Restart) { + restartLanguageClient(); + } + }; + } + + function _getOnErrorHandler(actionController, stopLanguageClient) { + return function (errorData) { + var action = actionController.getOnErrorAction(errorData); + + if (action === Actions.OnError.Stop) { + stopLanguageClient(); + } + }; + } + + function Logger() {} + + Logger.prototype.error = function (message) { + console.error(message); + }; + Logger.prototype.warn = function (message) { + console.warn(message); + }; + Logger.prototype.info = function (message) { + console.info(message); + }; + Logger.prototype.log = function (message) { + console.log(message); + }; + + function createConnection(reader, writer, restartLanguageClient, stopLanguageClient) { + var logger = new Logger(), + actionController = new ActionController(), + connection = protocol.createProtocolConnection(reader, writer, logger), + errorHandler = _getOnErrorHandler(actionController, stopLanguageClient), + closeHandler = _getOnCloseHandler(connection, actionController, restartLanguageClient); + + connection.onError(errorHandler); + connection.onClose(closeHandler); + + return connection; + } + + exports.createConnection = createConnection; +}()); diff --git a/src/languageTools/LanguageClient/LanguageClient.js b/src/languageTools/LanguageClient/LanguageClient.js new file mode 100644 index 00000000000..a8e4888ed0d --- /dev/null +++ b/src/languageTools/LanguageClient/LanguageClient.js @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports, Promise, LanguageClientInfo */ +/*eslint no-console: 0*/ +/*eslint strict: ["error", "global"]*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +"use strict"; + +var ProtocolAdapter = require("./ProtocolAdapter"), + ServerUtils = require("./ServerUtils"), + Connection = require("./Connection"), + NodeToBracketsInterface = require("./NodeToBracketsInterface").NodeToBracketsInterface, + ToolingInfo = LanguageClientInfo.toolingInfo, + MESSAGE_TYPE = { + BRACKETS: "brackets", + SERVER: "server" + }; + +function validateHandler(handler) { + var retval = false; + + if (handler && typeof handler === "function") { + retval = true; + } else { + console.warn("Handler validation failed. Handler should be of type 'function'. Provided handler is of type :", typeof handler); + } + + return retval; +} + +function LanguageClient(clientName, domainManager, options) { + this._clientName = clientName; + this._bracketsInterface = null; + this._notifyBrackets = null; + this._requestBrackets = null; + this._connection = null, + this._startUpParams = null, //_projectRoot, capabilties, workspaceFolders etc. + this._initialized = false, + this._onRequestHandler = {}; + this._onNotificationHandlers = {}; + this._options = options || null; + + + this._init(domainManager); +} + + +LanguageClient.prototype._createConnection = function () { + if (!this._options || !this._options.serverOptions) { + return Promise.reject("No valid options provided for client :", this._clientName); + } + + var restartLanguageClient = this.start.bind(this), + stopLanguageClient = this.stop.bind(this); + + var serverOptions = this._options.serverOptions; + return ServerUtils.startServerAndGetConnectionArgs(serverOptions) + .then(function (connectionArgs) { + return Connection.createConnection(connectionArgs.reader, connectionArgs.writer, restartLanguageClient, stopLanguageClient); + }).catch(function (err) { + console.error("Couldn't establish connection", err); + }); +}; + +LanguageClient.prototype.setOptions = function (options) { + if (options && typeof options === "object") { + this._options = options; + } else { + console.error("Invalid options provided for client :", this._clientName); + } +}; + +LanguageClient.prototype.start = function (params) { + var self = this; + + //Check to see if a connection to a language server already exists. + if (self._connection) { + return Promise.resolve(true); + } + + self._connection = null; + self._startUpParams = params || self._startUpParams; + + //We default to standard capabilties + if (!self._startUpParams.capabilities) { + self._startUpParams.capabilities = LanguageClientInfo.defaultBracketsCapabilities; + } + + return self._createConnection() + .then(function (connection) { + connection.listen(); + self._connection = connection; + + return ProtocolAdapter.initialize(connection, self._startUpParams); + }).then(function (result) { + self._initialized = result; + ProtocolAdapter.attachOnNotificationHandlers(self._connection, self._notifyBrackets); + ProtocolAdapter.attachOnRequestHandlers(self._connection, self._requestBrackets); + ProtocolAdapter.initialized(self._connection); + return result; + }).catch(function (error) { + console.error('Starting client failed because :', error); + console.error('Couldn\'t start client :', self._clientName); + + return error; + }); +}; + +LanguageClient.prototype.stop = function () { + var self = this; + + self._initialized = false; + if (!self._connection) { + return Promise.resolve(true); + } + + + return ProtocolAdapter.shutdown(self._connection).then(function () { + ProtocolAdapter.exit(self._connection); + self._connection.dispose(); + self._connection = null; + }); +}; + +LanguageClient.prototype.request = function (params) { + var messageParams = params.params; + if (messageParams && messageParams.messageType === MESSAGE_TYPE.BRACKETS) { + if (!messageParams.type) { + console.log("Invalid brackets request"); + return Promise.reject(); + } + + var requestHandler = this._onRequestHandler[messageParams.type]; + if(validateHandler(requestHandler)) { + return requestHandler.call(null, messageParams.params); + } + console.log("No handler provided for brackets request type : ", messageParams.type); + return Promise.reject(); + } + return ProtocolAdapter.processRequest(this._connection, params); + +}; + +LanguageClient.prototype.notify = function (params) { + var messageParams = params.params; + if (messageParams && messageParams.messageType === MESSAGE_TYPE.BRACKETS) { + if (!messageParams.type) { + console.log("Invalid brackets notification"); + return; + } + + var notificationHandlers = this._onNotificationHandlers[messageParams.type]; + if(notificationHandlers && Array.isArray(notificationHandlers) && notificationHandlers.length) { + notificationHandlers.forEach(function (handler) { + if(validateHandler(handler)) { + handler.call(null, messageParams.params); + } + }); + } else { + console.log("No handlers provided for brackets notification type : ", messageParams.type); + } + } else { + ProtocolAdapter.processNotification(this._connection, params); + } +}; + +LanguageClient.prototype.addOnRequestHandler = function (type, handler) { + if (validateHandler(handler)) { + this._onRequestHandler[type] = handler; + } +}; + +LanguageClient.prototype.addOnNotificationHandler = function (type, handler) { + if (validateHandler(handler)) { + if (!this._onNotificationHandlers[type]) { + this._onNotificationHandlers[type] = []; + } + + this._onNotificationHandlers[type].push(handler); + } +}; + +LanguageClient.prototype._init = function (domainManager) { + this._bracketsInterface = new NodeToBracketsInterface(domainManager, this._clientName); + + //Expose own methods for interfaceing. All these are async except notify. + this._bracketsInterface.registerMethods([ + { + methodName: ToolingInfo.LANGUAGE_SERVICE.START, + methodHandle: this.start.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.STOP, + methodHandle: this.stop.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.REQUEST, + methodHandle: this.request.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.NOTIFY, + methodHandle: this.notify.bind(this) + } + ]); + + //create function interfaces for Brackets + this._notifyBrackets = this._bracketsInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.NOTIFY); + this._requestBrackets = this._bracketsInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.REQUEST, true); +}; + +exports.LanguageClient = LanguageClient; diff --git a/src/languageTools/LanguageClient/NodeToBracketsInterface.js b/src/languageTools/LanguageClient/NodeToBracketsInterface.js new file mode 100644 index 00000000000..80d85e96c12 --- /dev/null +++ b/src/languageTools/LanguageClient/NodeToBracketsInterface.js @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global require, Promise, exports*/ +/*eslint no-invalid-this: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +(function () { + + "use strict"; + + var EventEmitter = require("events"), + bracketsEventHandler = new EventEmitter(); + + /** https://gist.github.com/LeverOne/1308368 */ + /*eslint-disable */ + function _generateUUID() { + var result, + numericSeed; + for ( + result = numericSeed = ''; + numericSeed++ < 36; + result += numericSeed * 51 & 52 ? (numericSeed ^ 15 ? 8 ^ Math.random() * (numericSeed ^ 20 ? 16 : 4) : 4).toString(16) : '-' + ); + + return result; + } + /*eslint-enable */ + + function NodeToBracketsInterface(domainManager, domainName) { + this.domainManager = domainManager; + this.domainName = domainName; + this.nodeFn = {}; + + this._registerDataEvents(domainManager, domainName); + } + + NodeToBracketsInterface.prototype.processRequest = function (params) { + var methodName = params.method; + if (this.nodeFn[methodName]) { + var method = this.nodeFn[methodName]; + return method.call(null, params.params); + } + }; + + NodeToBracketsInterface.prototype.processAsyncRequest = function (params, resolver) { + var methodName = params.method; + if (this.nodeFn[methodName]) { + var method = this.nodeFn[methodName]; + method.call(null, params.params) //The Async function should return a promise + .then(function (result) { + resolver(null, result); + }).catch(function (err) { + resolver(err, null); + }); + } + }; + + NodeToBracketsInterface.prototype.processResponse = function (params) { + if (params.requestId) { + if (params.error) { + bracketsEventHandler.emit(params.requestId, params.error); + } else { + bracketsEventHandler.emit(params.requestId, false, params.params); + } + } else { + bracketsEventHandler.emit(params.requestId, "error"); + } + }; + + NodeToBracketsInterface.prototype.createInterface = function (methodName, respond) { + var self = this; + return function (params) { + var callObject = { + method: methodName, + params: params + }; + + var retval = undefined; + if (respond) { + var requestId = _generateUUID(); + + callObject["respond"] = true; + callObject["requestId"] = requestId; + + self.domainManager.emitEvent(self.domainName, "data", callObject); + + retval = new Promise(function (resolve, reject) { + bracketsEventHandler.once(requestId, function (err, response) { + if (err) { + reject(err); + } else { + resolve(response); + } + }); + }); + } else { + self.domainManager.emitEvent(self.domainName, "data", callObject); + } + return retval; + }; + }; + + NodeToBracketsInterface.prototype.registerMethod = function (methodName, methodHandle) { + var self = this; + if (methodName && methodHandle && + typeof methodName === "string" && typeof methodHandle === "function") { + self.nodeFn[methodName] = methodHandle; + } + }; + + NodeToBracketsInterface.prototype.registerMethods = function (methodList) { + var self = this; + methodList.forEach(function (methodObj) { + self.registerMethod(methodObj.methodName, methodObj.methodHandle); + }); + }; + + NodeToBracketsInterface.prototype._registerDataEvents = function (domainManager, domainName) { + if (!domainManager.hasDomain(domainName)) { + domainManager.registerDomain(domainName, { + major: 0, + minor: 1 + }); + } + + domainManager.registerCommand( + domainName, + "data", + this.processRequest.bind(this), + false, + "Receives sync request from brackets", + [ + { + name: "params", + type: "object", + description: "json object containing message info" + } + ], + [] + ); + + domainManager.registerCommand( + domainName, + "response", + this.processResponse.bind(this), + false, + "Receives response from brackets for an earlier request", + [ + { + name: "params", + type: "object", + description: "json object containing message info" + } + ], + [] + ); + + domainManager.registerCommand( + domainName, + "asyncData", + this.processAsyncRequest.bind(this), + true, + "Receives async call request from brackets", + [ + { + name: "params", + type: "object", + description: "json object containing message info" + }, + { + name: "resolver", + type: "function", + description: "callback required to resolve the async request" + } + ], + [] + ); + + domainManager.registerEvent( + domainName, + "data", + [ + { + name: "params", + type: "object", + description: "json object containing message info to pass to brackets" + } + ] + ); + }; + + exports.NodeToBracketsInterface = NodeToBracketsInterface; +}()); diff --git a/src/languageTools/LanguageClient/ProtocolAdapter.js b/src/languageTools/LanguageClient/ProtocolAdapter.js new file mode 100644 index 00000000000..e9eabb35aaa --- /dev/null +++ b/src/languageTools/LanguageClient/ProtocolAdapter.js @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global LanguageClientInfo*/ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint no-fallthrough: 0*/ +"use strict"; + +var protocol = require("vscode-languageserver-protocol"), + Utils = require("./Utils"), + ToolingInfo = LanguageClientInfo.toolingInfo, + MESSAGE_FORMAT = { + BRACKETS: "brackets", + LSP: "lsp" + }; + +function _constructParamsAndRelay(relay, type, params) { + var _params = null, + handler = null; + + //Check for param object format. We won't change anything if the object is preformatted. + if (params.format === MESSAGE_FORMAT.LSP) { + params.format = undefined; + _params = JSON.parse(JSON.stringify(params)); + } + + switch (type) { + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST: + return sendCustomRequest(relay, params.type, params.params); + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION: + { + sendCustomNotification(relay, params.type, params.params); + break; + } + case ToolingInfo.SERVICE_REQUESTS.SHOW_SELECT_MESSAGE: + case ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST: + case ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST: + case ToolingInfo.SERVICE_REQUESTS.PROJECT_FOLDERS_REQUEST: + { + _params = { + type: type, + params: params + }; + return relay(_params); + } + case ToolingInfo.SERVICE_NOTIFICATIONS.SHOW_MESSAGE: + case ToolingInfo.SERVICE_NOTIFICATIONS.LOG_MESSAGE: + case ToolingInfo.SERVICE_NOTIFICATIONS.TELEMETRY: + case ToolingInfo.SERVICE_NOTIFICATIONS.DIAGNOSTICS: + { + _params = { + type: type, + params: params + }; + relay(_params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath), + languageId: params.languageId, + version: 1, + text: params.fileContent + } + }; + didOpenTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath), + version: 1 + }, + contentChanges: [{ + text: params.fileContent + }] + }; + didChangeTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED: + { + if (!_params) { + _params = { + textDocument: { + uri: Utils.pathToUri(params.filePath) + } + }; + + if (params.fileContent) { + _params['text'] = params.fileContent; + } + } + didSaveTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CLOSED: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + } + }; + + didCloseTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED: + { + var foldersAdded = params.foldersAdded || [], + foldersRemoved = params.foldersRemoved || []; + + foldersAdded = Utils.convertToWorkspaceFolders(foldersAdded); + foldersRemoved = Utils.convertToWorkspaceFolders(foldersRemoved); + + _params = _params || { + event: { + added: foldersAdded, + removed: foldersRemoved + } + }; + didChangeWorkspaceFolders(relay, _params); + break; + } + case ToolingInfo.FEATURES.CODE_HINTS: + handler = completion; + case ToolingInfo.FEATURES.PARAMETER_HINTS: + handler = handler || signatureHelp; + case ToolingInfo.FEATURES.JUMP_TO_DECLARATION: + handler = handler || gotoDeclaration; + case ToolingInfo.FEATURES.JUMP_TO_DEFINITION: + handler = handler || gotoDefinition; + case ToolingInfo.FEATURES.JUMP_TO_IMPL: + { + handler = handler || gotoImplementation; + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + }, + position: Utils.convertToLSPPosition(params.cursorPos) + }; + + return handler(relay, _params); + } + case ToolingInfo.FEATURES.CODE_HINT_INFO: + { + return completionItemResolve(relay, params); + } + case ToolingInfo.FEATURES.FIND_REFERENCES: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + }, + position: Utils.convertToLSPPosition(params.cursorPos), + context: { + includeDeclaration: params.includeDeclaration + } + }; + + return findReferences(relay, _params); + } + case ToolingInfo.FEATURES.DOCUMENT_SYMBOLS: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + } + }; + + return documentSymbol(relay, _params); + } + case ToolingInfo.FEATURES.PROJECT_SYMBOLS: + { + _params = _params || { + query: params.query + }; + + return workspaceSymbol(relay, _params); + } + } +} + +/** For custom messages */ +function onCustom(connection, type, handler) { + connection.onNotification(type, handler); +} + +function sendCustomRequest(connection, type, params) { + return connection.sendRequest(type, params); +} + +function sendCustomNotification(connection, type, params) { + connection.sendNotification(type, params); +} + +/** For Notification messages */ +function didOpenTextDocument(connection, params) { + connection.sendNotification(protocol.DidOpenTextDocumentNotification.type, params); +} + +function didChangeTextDocument(connection, params) { + connection.sendNotification(protocol.DidChangeTextDocumentNotification.type, params); +} + +function didCloseTextDocument(connection, params) { + connection.sendNotification(protocol.DidCloseTextDocumentNotification.type, params); +} + +function didSaveTextDocument(connection, params) { + connection.sendNotification(protocol.DidSaveTextDocumentNotification.type, params); +} + +function didChangeWorkspaceFolders(connection, params) { + connection.sendNotification(protocol.DidChangeWorkspaceFoldersNotification.type, params); +} + +/** For Request messages */ +function completion(connection, params) { + return connection.sendRequest(protocol.CompletionRequest.type, params); +} + +function completionItemResolve(connection, params) { + return connection.sendRequest(protocol.CompletionResolveRequest.type, params); +} + +function signatureHelp(connection, params) { + return connection.sendRequest(protocol.SignatureHelpRequest.type, params); +} + +function gotoDefinition(connection, params) { + return connection.sendRequest(protocol.DefinitionRequest.type, params); +} + +function gotoDeclaration(connection, params) { + return connection.sendRequest(protocol.DeclarationRequest.type, params); +} + +function gotoImplementation(connection, params) { + return connection.sendRequest(protocol.ImplementationRequest.type, params); +} + +function findReferences(connection, params) { + return connection.sendRequest(protocol.ReferencesRequest.type, params); +} + +function documentSymbol(connection, params) { + return connection.sendRequest(protocol.DocumentSymbolRequest.type, params); +} + +function workspaceSymbol(connection, params) { + return connection.sendRequest(protocol.WorkspaceSymbolRequest.type, params); +} + +/** + * Server commands + */ +function initialize(connection, params) { + var rootPath = params.rootPath, + workspaceFolders = params.rootPaths; + + if(!rootPath && workspaceFolders && Array.isArray(workspaceFolders)) { + rootPath = workspaceFolders[0]; + } + + if (!workspaceFolders) { + workspaceFolders = [rootPath]; + } + + if (workspaceFolders.length) { + workspaceFolders = Utils.convertToWorkspaceFolders(workspaceFolders); + } + + var _params = { + rootPath: rootPath, + rootUri: Utils.pathToUri(rootPath), + processId: process.pid, + capabilities: params.capabilities, + workspaceFolders: workspaceFolders + }; + + return connection.sendRequest(protocol.InitializeRequest.type, _params); +} + +function initialized(connection) { + connection.sendNotification(protocol.InitializedNotification.type); +} + +function shutdown(connection) { + return connection.sendRequest(protocol.ShutdownRequest.type); +} + +function exit(connection) { + connection.sendNotification(protocol.ExitNotification.type); +} + +function processRequest(connection, message) { + return _constructParamsAndRelay(connection, message.type, message.params); +} + +function processNotification(connection, message) { + _constructParamsAndRelay(connection, message.type, message.params); +} + +function attachOnNotificationHandlers(connection, handler) { + function _callbackFactory(type) { + switch (type) { + case protocol.ShowMessageNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.SHOW_MESSAGE); + case protocol.LogMessageNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.LOG_MESSAGE); + case protocol.TelemetryEventNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.TELEMETRY); + case protocol.PublishDiagnosticsNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.DIAGNOSTICS); + } + } + + connection.onNotification(protocol.ShowMessageNotification.type, _callbackFactory(protocol.ShowMessageNotification.type)); + connection.onNotification(protocol.LogMessageNotification.type, _callbackFactory(protocol.LogMessageNotification.type)); + connection.onNotification(protocol.TelemetryEventNotification.type, _callbackFactory(protocol.TelemetryEventNotification.type)); + connection.onNotification(protocol.PublishDiagnosticsNotification.type, _callbackFactory(protocol.PublishDiagnosticsNotification.type)); + connection.onNotification(function (type, params) { + var _params = { + type: type, + params: params + }; + handler(_params); + }); +} + +function attachOnRequestHandlers(connection, handler) { + function _callbackFactory(type) { + switch (type) { + case protocol.ShowMessageRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.SHOW_SELECT_MESSAGE); + case protocol.RegistrationRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST); + case protocol.UnregistrationRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST); + case protocol.WorkspaceFoldersRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.PROJECT_FOLDERS_REQUEST); + } + } + + connection.onRequest(protocol.ShowMessageRequest.type, _callbackFactory(protocol.ShowMessageRequest.type)); + connection.onRequest(protocol.RegistrationRequest.type, _callbackFactory(protocol.RegistrationRequest.type)); + // See https://github.com/Microsoft/vscode-languageserver-node/issues/199 + connection.onRequest("client/registerFeature", _callbackFactory(protocol.RegistrationRequest.type)); + connection.onRequest(protocol.UnregistrationRequest.type, _callbackFactory(protocol.UnregistrationRequest.type)); + // See https://github.com/Microsoft/vscode-languageserver-node/issues/199 + connection.onRequest("client/unregisterFeature", _callbackFactory(protocol.UnregistrationRequest.type)); + connection.onRequest(protocol.WorkspaceFoldersRequest.type, _callbackFactory(protocol.WorkspaceFoldersRequest.type)); + connection.onRequest(function (type, params) { + var _params = { + type: type, + params: params + }; + return handler(_params); + }); +} + +exports.initialize = initialize; +exports.initialized = initialized; +exports.shutdown = shutdown; +exports.exit = exit; +exports.onCustom = onCustom; +exports.sendCustomRequest = sendCustomRequest; +exports.sendCustomNotification = sendCustomNotification; +exports.processRequest = processRequest; +exports.processNotification = processNotification; +exports.attachOnNotificationHandlers = attachOnNotificationHandlers; +exports.attachOnRequestHandlers = attachOnRequestHandlers; diff --git a/src/languageTools/LanguageClient/ServerUtils.js b/src/languageTools/LanguageClient/ServerUtils.js new file mode 100644 index 00000000000..3e865a5c1a7 --- /dev/null +++ b/src/languageTools/LanguageClient/ServerUtils.js @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports, process, Promise, __dirname, global*/ +/*eslint no-console: 0*/ +/*eslint no-fallthrough: 0*/ +/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ +(function () { + "use strict"; + + var protocol = require("vscode-languageserver-protocol"), + cp = require("child_process"), + fs = require("fs"); + + var CommunicationTypes = { + NodeIPC: { + type: "ipc", + flag: "--node-ipc" + }, + StandardIO: { + type: "stdio", + flag: "--stdio" + }, + Pipe: { + type: "pipe", + flag: "--pipe" + }, + Socket: { + type: "socket", + flag: "--socket" + } + }, + CLIENT_PROCESS_ID_FLAG = "--clientProcessId"; + + function addCommunicationArgs(communication, processArgs, isRuntime) { + switch (communication) { + case CommunicationTypes.NodeIPC.type: + { + if (isRuntime) { + processArgs.options.stdio = [null, null, null, 'ipc']; + processArgs.args.push(CommunicationTypes.NodeIPC.flag); + } else { + processArgs.args.push(CommunicationTypes.NodeIPC.flag); + } + break; + } + case CommunicationTypes.StandardIO.type: + { + processArgs.args.push(CommunicationTypes.StandardIO.flag); + break; + } + case CommunicationTypes.Pipe.type: + { + var pipeName = protocol.generateRandomPipeName(), + pipeflag = CommunicationTypes.Pipe.flag + "=" + pipeName.toString(); + + processArgs.args.push(pipeflag); + processArgs.pipeName = pipeName; + break; + } + default: + { + if (communication && communication.type === CommunicationTypes.Socket.type) { + var socketFlag = CommunicationTypes.Socket.flag + "=" + communication.port.toString(); + processArgs.args.push(socketFlag); + } + } + } + + var clientProcessIdFlag = CLIENT_PROCESS_ID_FLAG + "=" + process.pid.toString(); + processArgs.args.push(clientProcessIdFlag); + } + + function _getEnvironment(env) { + if (!env) { + return process.env; + } + + //Combine env vars + var result = Object.assign({}, process.env, env); + return result; + } + + function _createReaderAndWriteByCommunicationType(resp, type) { + var retval = null; + + switch (type) { + case CommunicationTypes.NodeIPC.type: + { + if (resp.process) { + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + + resp.process.stdout.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.info('[Server logs @ stdout] "%s"', String(data)); + } + }); + + retval = { + reader: new protocol.IPCMessageReader(resp.process), + writer: new protocol.IPCMessageWriter(resp.process) + }; + } + break; + } + case CommunicationTypes.StandardIO.type: + { + if (resp.process) { + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + + retval = { + reader: new protocol.StreamMessageReader(resp.process.stdout), + writer: new protocol.StreamMessageWriter(resp.process.stdin) + }; + } + break; + } + case CommunicationTypes.Pipe.type: + case CommunicationTypes.Socket.type: + { + if (resp.reader && resp.writer && resp.process) { + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + + resp.process.stdout.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.info('[Server logs @ stdout] "%s"', String(data)); + } + }); + + retval = { + reader: resp.reader, + writer: resp.writer + }; + } + } + } + + return retval; + } + + function _createReaderAndWriter(resp) { + var retval = null; + + if (!resp) { + return retval; + } + + if (resp.reader && resp.writer) { + retval = { + reader: resp.reader, + writer: resp.writer + }; + } else if (resp.process) { + retval = { + reader: new protocol.StreamMessageReader(resp.process.stdout), + writer: new protocol.StreamMessageWriter(resp.process.stdin) + }; + + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + } + + return retval; + } + + function _isServerProcessValid(serverProcess) { + if (!serverProcess || !serverProcess.pid) { + return false; + } + + return true; + } + + function _startServerAndGetTransports(communication, processArgs, isRuntime) { + return new Promise(function (resolve, reject) { + var serverProcess = null, + result = null, + protocolTransport = null, + type = typeof communication === "object" ? communication.type : communication; + + var processFunc = isRuntime ? cp.spawn : cp.fork; + + switch (type) { + case CommunicationTypes.NodeIPC.type: + case CommunicationTypes.StandardIO.type: + { + serverProcess = processFunc(processArgs.primaryArg, processArgs.args, processArgs.options); + if (_isServerProcessValid(serverProcess)) { + result = _createReaderAndWriteByCommunicationType({ + process: serverProcess + }, type); + + resolve(result); + } else { + reject(null); + } + break; + } + case CommunicationTypes.Pipe.type: + { + protocolTransport = protocol.createClientPipeTransport(processArgs.pipeName); + } + case CommunicationTypes.Socket.type: + { + if (communication && communication.type === CommunicationTypes.Socket.type) { + protocolTransport = protocol.createClientSocketTransport(communication.port); + } + + if (!protocolTransport) { + reject("Invalid Communications Object. Can't create connection with server"); + return; + } + + protocolTransport.then(function (transportObj) { + serverProcess = processFunc(processArgs.primaryArg, processArgs.args, processArgs.options); + if (_isServerProcessValid(serverProcess)) { + transportObj.onConnected().then(function (protocolObj) { + result = _createReaderAndWriteByCommunicationType({ + process: serverProcess, + reader: protocolObj[0], + writer: protocolObj[1] + }, type); + + resolve(result); + }).catch(reject); + } + }).catch(reject); + } + } + }); + } + + function _handleOtherRuntime(serverOptions) { + function _getArguments(sOptions) { + var args = []; + + if (sOptions.options && sOptions.options.execArgv) { + args = args.concat(sOptions.options.execArgv); + } + + args.push(sOptions.module); + if (sOptions.args) { + args = args.concat(sOptions.args); + } + + return args; + } + + function _getOptions(sOptions) { + var cwd = undefined, + env = undefined; + + if (sOptions.options) { + if (sOptions.options.cwd) { + try { + if (fs.lstatSync(sOptions.options.cwd).isDirectory(sOptions.options.cwd)) { + cwd = sOptions.options.cwd; + } + } catch (e) {} + } + + cwd = cwd || __dirname; + if (sOptions.options.env) { + env = sOptions.options.env; + } + } + + var options = { + cwd: cwd, + env: _getEnvironment(env) + }; + + return options; + } + + var communication = serverOptions.communication || CommunicationTypes.StandardIO.type, + args = _getArguments(serverOptions), + options = _getOptions(serverOptions), + processArgs = { + args: args, + options: options, + primaryArg: serverOptions.runtime + }; + + addCommunicationArgs(communication, processArgs, true); + return _startServerAndGetTransports(communication, processArgs, true); + } + + function _handleNodeRuntime(serverOptions) { + function _getArguments(sOptions) { + var args = []; + + if (sOptions.args) { + args = args.concat(sOptions.args); + } + + return args; + } + + function _getOptions(sOptions) { + var cwd = undefined; + + if (sOptions.options) { + if (sOptions.options.cwd) { + try { + if (fs.lstatSync(sOptions.options.cwd).isDirectory(sOptions.options.cwd)) { + cwd = sOptions.options.cwd; + } + } catch (e) {} + } + cwd = cwd || __dirname; + } + + var options = Object.assign({}, sOptions.options); + options.cwd = cwd, + options.execArgv = options.execArgv || []; + options.silent = true; + + return options; + } + + var communication = serverOptions.communication || CommunicationTypes.StandardIO.type, + args = _getArguments(serverOptions), + options = _getOptions(serverOptions), + processArgs = { + args: args, + options: options, + primaryArg: serverOptions.module + }; + + addCommunicationArgs(communication, processArgs, false); + return _startServerAndGetTransports(communication, processArgs, false); + } + + + function _handleServerFunction(func) { + return func().then(function (resp) { + var result = _createReaderAndWriter(resp); + + return result; + }); + } + + function _handleModules(serverOptions) { + if (serverOptions.runtime) { + return _handleOtherRuntime(serverOptions); + } + return _handleNodeRuntime(serverOptions); + + } + + function _handleExecutable(serverOptions) { + return new Promise(function (resolve, reject) { + var command = serverOptions.command, + args = serverOptions.args, + options = Object.assign({}, serverOptions.options); + + var serverProcess = cp.spawn(command, args, options); + if (!serverProcess || !serverProcess.pid) { + reject("Failed to launch server using command :", command); + } + + var result = _createReaderAndWriter({ + process: serverProcess, + detached: !!options.detached + }); + + if (result) { + resolve(result); + } else { + reject(result); + } + }); + } + + function startServerAndGetConnectionArgs(serverOptions) { + if (typeof serverOptions === "function") { + return _handleServerFunction(serverOptions); + } else if (typeof serverOptions === "object") { + if (serverOptions.module) { + return _handleModules(serverOptions); + } else if (serverOptions.command) { + return _handleExecutable(serverOptions); + } + } + + return Promise.reject(null); + } + + + exports.startServerAndGetConnectionArgs = startServerAndGetConnectionArgs; +}()); diff --git a/src/languageTools/LanguageClient/Utils.js b/src/languageTools/LanguageClient/Utils.js new file mode 100644 index 00000000000..28df1c0b682 --- /dev/null +++ b/src/languageTools/LanguageClient/Utils.js @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint-env es6, node*/ +"use strict"; + +var nodeURL = require("url"), + path = require("path"); + +function pathToUri(filePath) { + var newPath = convertWinToPosixPath(filePath); + if (newPath[0] !== '/') { + newPath = `/${newPath}`; + } + return encodeURI(`file://${newPath}`).replace(/[?#]/g, encodeURIComponent); +} + +function uriToPath(uri) { + var url = nodeURL.URL.parse(uri); + if (url.protocol !== 'file:' || url.path === undefined) { + return uri; + } + + let filePath = decodeURIComponent(url.path); + if (process.platform === 'win32') { + if (filePath[0] === '/') { + filePath = filePath.substr(1); + } + return filePath; + } + return filePath; +} + +function convertPosixToWinPath(filePath) { + return filePath.replace(/\//g, '\\'); +} + +function convertWinToPosixPath(filePath) { + return filePath.replace(/\\/g, '/'); +} + +function convertToLSPPosition(pos) { + return { + line: pos.line, + character: pos.ch + }; +} + +function convertToWorkspaceFolders(paths) { + var workspaceFolders = paths.map(function (folderPath) { + var uri = pathToUri(folderPath), + name = path.basename(folderPath); + + return { + uri: uri, + name: name + }; + }); + + return workspaceFolders; +} + +exports.uriToPath = uriToPath; +exports.pathToUri = pathToUri; +exports.convertPosixToWinPath = convertPosixToWinPath; +exports.convertWinToPosixPath = convertWinToPosixPath; +exports.convertToLSPPosition = convertToLSPPosition; +exports.convertToWorkspaceFolders = convertToWorkspaceFolders; diff --git a/src/languageTools/LanguageClient/package.json b/src/languageTools/LanguageClient/package.json new file mode 100644 index 00000000000..2fdc6b6094a --- /dev/null +++ b/src/languageTools/LanguageClient/package.json @@ -0,0 +1,19 @@ +{ + "name": "brackets-language-client", + "version": "1.0.0", + "description": "Brackets language client interface for Language Server Protocol", + "main": "LanguageClient.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "LSP", + "LanguageClient", + "Brackets" + ], + "author": "Adobe", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "^3.14.1" + } +} diff --git a/src/languageTools/LanguageClientWrapper.js b/src/languageTools/LanguageClientWrapper.js new file mode 100644 index 00000000000..abd0c9b41f5 --- /dev/null +++ b/src/languageTools/LanguageClientWrapper.js @@ -0,0 +1,627 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-console: 0*/ +/*eslint indent: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var ToolingInfo = JSON.parse(require("text!languageTools/ToolingInfo.json")), + MESSAGE_FORMAT = { + BRACKETS: "brackets", + LSP: "lsp" + }; + + function _addTypeInformation(type, params) { + return { + type: type, + params: params + }; + } + + function hasValidProp(obj, prop) { + return (obj && obj[prop] !== undefined && obj[prop] !== null); + } + + function hasValidProps(obj, props) { + var retval = !!obj, + len = props.length, + i; + + for (i = 0; retval && (i < len); i++) { + retval = (retval && obj[props[i]] !== undefined && obj[props[i]] !== null); + } + + return retval; + } + /* + RequestParams creator - sendNotifications/request + */ + function validateRequestParams(type, params) { + var validatedParams = null; + + params = params || {}; + + //Don't validate if the formatting is done by the caller + if (params.format === MESSAGE_FORMAT.LSP) { + return params; + } + + switch (type) { + case ToolingInfo.LANGUAGE_SERVICE.START: + { + if (hasValidProp(params, "rootPaths") || hasValidProp(params, "rootPath")) { + validatedParams = params; + validatedParams.capabilities = validatedParams.capabilities || false; + } + break; + } + case ToolingInfo.FEATURES.CODE_HINTS: + case ToolingInfo.FEATURES.PARAMETER_HINTS: + case ToolingInfo.FEATURES.JUMP_TO_DECLARATION: + case ToolingInfo.FEATURES.JUMP_TO_DEFINITION: + case ToolingInfo.FEATURES.JUMP_TO_IMPL: + { + if (hasValidProps(params, ["filePath", "cursorPos"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.FEATURES.CODE_HINT_INFO: + { + validatedParams = params; + break; + } + case ToolingInfo.FEATURES.FIND_REFERENCES: + { + if (hasValidProps(params, ["filePath", "cursorPos"])) { + validatedParams = params; + validatedParams.includeDeclaration = validatedParams.includeDeclaration || false; + } + break; + } + case ToolingInfo.FEATURES.DOCUMENT_SYMBOLS: + { + if (hasValidProp(params, "filePath")) { + validatedParams = params; + } + break; + } + case ToolingInfo.FEATURES.PROJECT_SYMBOLS: + { + if (params && params.query && typeof params.query === "string") { + validatedParams = params; + } + break; + } + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST: + { + validatedParams = params; + } + } + + return validatedParams; + } + + /* + ReponseParams transformer - used by OnNotifications + */ + function validateNotificationParams(type, params) { + var validatedParams = null; + + params = params || {}; + + //Don't validate if the formatting is done by the caller + if (params.format === MESSAGE_FORMAT.LSP) { + return params; + } + + switch (type) { + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED: + { + if (hasValidProps(params, ["filePath", "fileContent", "languageId"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED: + { + if (hasValidProps(params, ["filePath", "fileContent"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED: + { + if (hasValidProp(params, "filePath")) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CLOSED: + { + if (hasValidProp(params, "filePath")) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED: + { + if (hasValidProps(params, ["foldersAdded", "foldersRemoved"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION: + { + validatedParams = params; + } + } + + return validatedParams; + } + + function validateHandler(handler) { + var retval = false; + + if (handler && typeof handler === "function") { + retval = true; + } else { + console.warn("Handler validation failed. Handler should be of type 'function'. Provided handler is of type :", typeof handler); + } + + return retval; + } + + function LanguageClientWrapper(name, path, domainInterface, languages) { + this._name = name; + this._path = path; + this._domainInterface = domainInterface; + this._languages = languages || []; + this._startClient = null; + this._stopClient = null; + this._notifyClient = null; + this._requestClient = null; + this._onRequestHandler = {}; + this._onNotificationHandlers = {}; + this._dynamicCapabilities = {}; + this._serverCapabilities = {}; + + //Initialize with keys for brackets events we want to tap into. + this._onEventHandlers = { + "activeEditorChange": [], + "projectOpen": [], + "beforeProjectClose": [], + "dirtyFlagChange": [], + "documentChange": [], + "fileNameChange": [], + "beforeAppClose": [] + }; + + this._init(); + } + + LanguageClientWrapper.prototype._init = function () { + this._domainInterface.registerMethods([ + { + methodName: ToolingInfo.LANGUAGE_SERVICE.REQUEST, + methodHandle: this._onRequestDelegator.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.NOTIFY, + methodHandle: this._onNotificationDelegator.bind(this) + } + ]); + + //create function interfaces + this._startClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.START, true); + this._stopClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.STOP, true); + this._notifyClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.NOTIFY); + this._requestClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.REQUEST, true); + }; + + LanguageClientWrapper.prototype._onRequestDelegator = function (params) { + if (!params || !params.type) { + console.log("Invalid server request"); + return $.Deferred().reject(); + } + + var requestHandler = this._onRequestHandler[params.type]; + if (params.type === ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST) { + return this._registrationShim(params.params, requestHandler); + } + + if (params.type === ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST) { + return this._unregistrationShim(params.params, requestHandler); + } + + if (validateHandler(requestHandler)) { + return requestHandler.call(null, params.params); + } + console.log("No handler provided for server request type : ", params.type); + return $.Deferred().reject(); + + }; + + LanguageClientWrapper.prototype._onNotificationDelegator = function (params) { + if (!params || !params.type) { + console.log("Invalid server notification"); + return; + } + + var notificationHandlers = this._onNotificationHandlers[params.type]; + if (notificationHandlers && Array.isArray(notificationHandlers) && notificationHandlers.length) { + notificationHandlers.forEach(function (handler) { + if (validateHandler(handler)) { + handler.call(null, params.params); + } + }); + } else { + console.log("No handlers provided for server notification type : ", params.type); + } + }; + + LanguageClientWrapper.prototype._request = function (type, params) { + params = validateRequestParams(type, params); + if (params) { + params = _addTypeInformation(type, params); + return this._requestClient(params); + } + + console.log("Invalid Parameters provided for request type : ", type); + return $.Deferred().reject(); + }; + + LanguageClientWrapper.prototype._notify = function (type, params) { + params = validateNotificationParams(type, params); + if (params) { + params = _addTypeInformation(type, params); + this._notifyClient(params); + } else { + console.log("Invalid Parameters provided for notification type : ", type); + } + }; + + LanguageClientWrapper.prototype._addOnRequestHandler = function (type, handler) { + if (validateHandler(handler)) { + this._onRequestHandler[type] = handler; + } + }; + + LanguageClientWrapper.prototype._addOnNotificationHandler = function (type, handler) { + if (validateHandler(handler)) { + if (!this._onNotificationHandlers[type]) { + this._onNotificationHandlers[type] = []; + } + + this._onNotificationHandlers[type].push(handler); + } + }; + + /** + Requests + */ + //start + LanguageClientWrapper.prototype.start = function (params) { + params = validateRequestParams(ToolingInfo.LANGUAGE_SERVICE.START, params); + if (params) { + var self = this; + return this._startClient(params) + .then(function (result) { + self.setServerCapabilities(result.capabilities); + return $.Deferred().resolve(result); + }, function (err) { + return $.Deferred().reject(err); + }); + } + + console.log("Invalid Parameters provided for request type : start"); + return $.Deferred().reject(); + }; + + //shutdown + LanguageClientWrapper.prototype.stop = function () { + return this._stopClient(); + }; + + //restart + LanguageClientWrapper.prototype.restart = function (params) { + var self = this; + return this.stop().then(function () { + return self.start(params); + }); + }; + + /** + textDocument requests + */ + //completion + LanguageClientWrapper.prototype.requestHints = function (params) { + return this._request(ToolingInfo.FEATURES.CODE_HINTS, params); + }; + + //completionItemResolve + LanguageClientWrapper.prototype.getAdditionalInfoForHint = function (params) { + return this._request(ToolingInfo.FEATURES.CODE_HINT_INFO, params); + }; + + //signatureHelp + LanguageClientWrapper.prototype.requestParameterHints = function (params) { + return this._request(ToolingInfo.FEATURES.PARAMETER_HINTS, params); + }; + + //gotoDefinition + LanguageClientWrapper.prototype.gotoDefinition = function (params) { + return this._request(ToolingInfo.FEATURES.JUMP_TO_DEFINITION, params); + }; + + //gotoDeclaration + LanguageClientWrapper.prototype.gotoDeclaration = function (params) { + return this._request(ToolingInfo.FEATURES.JUMP_TO_DECLARATION, params); + }; + + //gotoImplementation + LanguageClientWrapper.prototype.gotoImplementation = function (params) { + return this._request(ToolingInfo.FEATURES.JUMP_TO_IMPL, params); + }; + + //findReferences + LanguageClientWrapper.prototype.findReferences = function (params) { + return this._request(ToolingInfo.FEATURES.FIND_REFERENCES, params); + }; + + //documentSymbol + LanguageClientWrapper.prototype.requestSymbolsForDocument = function (params) { + return this._request(ToolingInfo.FEATURES.DOCUMENT_SYMBOLS, params); + }; + + /** + workspace requests + */ + //workspaceSymbol + LanguageClientWrapper.prototype.requestSymbolsForWorkspace = function (params) { + return this._request(ToolingInfo.FEATURES.PROJECT_SYMBOLS, params); + }; + + //These will mostly be callbacks/[done-fail](promises) + /** + Window OnNotifications + */ + //showMessage + LanguageClientWrapper.prototype.addOnShowMessage = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.SHOW_MESSAGE, handler); + }; + + //logMessage + LanguageClientWrapper.prototype.addOnLogMessage = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.LOG_MESSAGE, handler); + }; + + /** + healthData/logging OnNotifications + */ + //telemetry + LanguageClientWrapper.prototype.addOnTelemetryEvent = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.TELEMETRY, handler); + }; + + /** + textDocument OnNotifications + */ + //onPublishDiagnostics + LanguageClientWrapper.prototype.addOnCodeInspection = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.DIAGNOSTICS, handler); + }; + + /** + Window OnRequest + */ + + //showMessageRequest - handler must return promise + LanguageClientWrapper.prototype.onShowMessageWithRequest = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.SHOW_SELECT_MESSAGE, handler); + }; + + LanguageClientWrapper.prototype.onProjectFoldersRequest = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.PROJECT_FOLDERS_REQUEST, handler); + }; + + LanguageClientWrapper.prototype._registrationShim = function (params, handler) { + var self = this; + + var registrations = params.registrations; + registrations.forEach(function (registration) { + var id = registration.id; + self._dynamicCapabilities[id] = registration; + }); + return validateHandler(handler) ? handler(params) : $.Deferred().resolve(); + }; + + LanguageClientWrapper.prototype.onDynamicCapabilityRegistration = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST, handler); + }; + + LanguageClientWrapper.prototype._unregistrationShim = function (params, handler) { + var self = this; + + var unregistrations = params.unregistrations; + unregistrations.forEach(function (unregistration) { + var id = unregistration.id; + delete self._dynamicCapabilities[id]; + }); + return validateHandler(handler) ? handler(params) : $.Deferred().resolve(); + }; + + LanguageClientWrapper.prototype.onDynamicCapabilityUnregistration = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST, handler); + }; + + /* + Unimplemented OnNotifications + workspace + applyEdit (codeAction, codeLens) + */ + + /** + SendNotifications + */ + + /** + workspace SendNotifications + */ + //didChangeProjectRoots + LanguageClientWrapper.prototype.notifyProjectRootsChanged = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED, params); + }; + + /** + textDocument SendNotifications + */ + //didOpenTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentOpened = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED, params); + }; + + //didCloseTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentClosed = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CLOSED, params); + }; + + //didChangeTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentChanged = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED, params); + }; + + //didSaveTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentSave = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED, params); + }; + + /** + Custom messages + */ + + //customNotification + LanguageClientWrapper.prototype.sendCustomNotification = function (params) { + this._notify(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION, params); + }; + + LanguageClientWrapper.prototype.onCustomNotification = function (type, handler) { + this._addOnNotificationHandler(type, handler); + }; + + //customRequest + LanguageClientWrapper.prototype.sendCustomRequest = function (params) { + return this._request(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST, params); + }; + + LanguageClientWrapper.prototype.onCustomRequest = function (type, handler) { + this._addOnRequestHandler(type, handler); + }; + + //Handling Brackets Events + LanguageClientWrapper.prototype.addOnEditorChangeHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["activeEditorChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnProjectOpenHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["projectOpen"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addBeforeProjectCloseHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["beforeProjectClose"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnDocumentDirtyFlagChangeHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["dirtyFlagChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnDocumentChangeHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["documentChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnFileRenameHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["fileNameChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addBeforeAppClose = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["beforeAppClose"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnCustomEventHandler = function (eventName, handler) { + if (validateHandler(handler)) { + if (!this._onEventHandlers[eventName]) { + this._onEventHandlers[eventName] = []; + } + this._onEventHandlers[eventName].push(handler); + } + }; + + LanguageClientWrapper.prototype.triggerEvent = function (event) { + var eventName = event.type, + eventArgs = arguments; + + if (this._onEventHandlers[eventName] && Array.isArray(this._onEventHandlers[eventName])) { + var handlers = this._onEventHandlers[eventName]; + + handlers.forEach(function (handler) { + if (validateHandler(handler)) { + handler.apply(null, eventArgs); + } + }); + } + }; + + LanguageClientWrapper.prototype.getDynamicCapabilities = function () { + return this._dynamicCapabilities; + }; + + LanguageClientWrapper.prototype.getServerCapabilities = function () { + return this._serverCapabilities; + }; + + LanguageClientWrapper.prototype.setServerCapabilities = function (serverCapabilities) { + this._serverCapabilities = serverCapabilities; + }; + + exports.LanguageClientWrapper = LanguageClientWrapper; + + //For unit testting + exports.validateRequestParams = validateRequestParams; + exports.validateNotificationParams = validateNotificationParams; +}); diff --git a/src/languageTools/LanguageTools.js b/src/languageTools/LanguageTools.js new file mode 100644 index 00000000000..08d2bca6b71 --- /dev/null +++ b/src/languageTools/LanguageTools.js @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-console: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint-env es6*/ +define(function (require, exports, module) { + "use strict"; + + var ClientLoader = require("languageTools/ClientLoader"), + EditorManager = require("editor/EditorManager"), + ProjectManager = require("project/ProjectManager"), + DocumentManager = require("document/DocumentManager"), + DocumentModule = require("document/Document"), + PreferencesManager = require("preferences/PreferencesManager"), + Strings = require("strings"), + LanguageClientWrapper = require("languageTools/LanguageClientWrapper").LanguageClientWrapper; + + var languageClients = new Map(), + languageToolsPrefs = { + showServerLogsInConsole: false + }, + BRACKETS_EVENTS_NAMES = { + EDITOR_CHANGE_EVENT: "activeEditorChange", + PROJECT_OPEN_EVENT: "projectOpen", + PROJECT_CLOSE_EVENT: "beforeProjectClose", + DOCUMENT_DIRTY_EVENT: "dirtyFlagChange", + DOCUMENT_CHANGE_EVENT: "documentChange", + FILE_RENAME_EVENT: "fileNameChange", + BEFORE_APP_CLOSE: "beforeAppClose" + }; + + PreferencesManager.definePreference("languageTools", "object", languageToolsPrefs, { + description: Strings.LANGUAGE_TOOLS_PREFERENCES + }); + + PreferencesManager.on("change", "languageTools", function () { + languageToolsPrefs = PreferencesManager.get("languageTools"); + + ClientLoader.syncPrefsWithDomain(languageToolsPrefs); + }); + + function registerLanguageClient(clientName, languageClient) { + languageClients.set(clientName, languageClient); + } + + function _withNamespace(event) { + return event.split(" ") + .filter((value) => !!value) + .map((value) => value + ".language-tools") + .join(" "); + } + + function _eventHandler() { + var eventArgs = arguments; + //Broadcast event to all clients + languageClients.forEach(function (client) { + client.triggerEvent.apply(client, eventArgs); + }); + } + + function _attachEventHandlers() { + //Attach standard listeners + EditorManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.EDITOR_CHANGE_EVENT), _eventHandler); //(event, current, previous) + ProjectManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.PROJECT_OPEN_EVENT), _eventHandler); //(event, directory) + ProjectManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.PROJECT_CLOSE_EVENT), _eventHandler); //(event, directory) + DocumentManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.DOCUMENT_DIRTY_EVENT), _eventHandler); //(event, document) + DocumentModule.on(_withNamespace(BRACKETS_EVENTS_NAMES.DOCUMENT_CHANGE_EVENT), _eventHandler); //(event, document, changeList) + DocumentManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.FILE_RENAME_EVENT), _eventHandler); //(event, oldName, newName) + ProjectManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.BEFORE_APP_CLOSE), _eventHandler); //(event, oldName, newName) + } + + _attachEventHandlers(); + + function listenToCustomEvent(eventModule, eventName) { + eventModule.on(_withNamespace(eventName), _eventHandler); + } + + function initiateToolingService(clientName, clientFilePath, languages) { + var result = $.Deferred(); + + ClientLoader.initiateLanguageClient(clientName, clientFilePath) + .done(function (languageClientInfo) { + var languageClientName = languageClientInfo.name, + languageClientInterface = languageClientInfo.interface, + languageClient = new LanguageClientWrapper(languageClientName, clientFilePath, languageClientInterface, languages); + + registerLanguageClient(languageClientName, languageClient); + + result.resolve(languageClient); + }) + .fail(result.reject); + + return result; + } + + exports.initiateToolingService = initiateToolingService; + exports.listenToCustomEvent = listenToCustomEvent; +}); diff --git a/src/languageTools/PathConverters.js b/src/languageTools/PathConverters.js new file mode 100644 index 00000000000..c4bbc7de898 --- /dev/null +++ b/src/languageTools/PathConverters.js @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint-disable indent */ +define(function (require, exports, module) { + "use strict"; + + var PathUtils = require("thirdparty/path-utils/path-utils"), + FileUtils = require("file/FileUtils"); + + function uriToPath(uri) { + var url = PathUtils.parseUrl(uri); + if (url.protocol !== 'file:' || url.pathname === undefined) { + return uri; + } + + let filePath = decodeURIComponent(url.pathname); + if (brackets.platform === 'win') { + if (filePath[0] === '/') { + filePath = filePath.substr(1); + } + return filePath; + } + return filePath; + } + + function pathToUri(filePath) { + var newPath = convertWinToPosixPath(filePath); + if (newPath[0] !== '/') { + newPath = `/${newPath}`; + } + return encodeURI(`file://${newPath}`).replace(/[?#]/g, encodeURIComponent); + } + + function convertToWorkspaceFolders(paths) { + var workspaceFolders = paths.map(function (folderPath) { + var uri = pathToUri(folderPath), + name = FileUtils.getBasename(folderPath); + + return { + uri: uri, + name: name + }; + }); + + return workspaceFolders; + } + + function convertPosixToWinPath(path) { + return path.replace(/\//g, '\\'); + } + + function convertWinToPosixPath(path) { + return path.replace(/\\/g, '/'); + } + + exports.uriToPath = uriToPath; + exports.pathToUri = pathToUri; + exports.convertPosixToWinPath = convertPosixToWinPath; + exports.convertPosixToWinPath = convertPosixToWinPath; + exports.convertToWorkspaceFolders = convertToWorkspaceFolders; +}); diff --git a/src/languageTools/ToolingInfo.json b/src/languageTools/ToolingInfo.json new file mode 100644 index 00000000000..d7457ec6a9e --- /dev/null +++ b/src/languageTools/ToolingInfo.json @@ -0,0 +1,41 @@ +{ + "LANGUAGE_SERVICE": { + "START": "start", + "STOP": "stop", + "REQUEST": "request", + "NOTIFY": "notify", + "CANCEL_REQUEST": "cancelRequest", + "CUSTOM_REQUEST": "customRequest", + "CUSTOM_NOTIFICATION": "customNotification" + }, + "SERVICE_NOTIFICATIONS": { + "SHOW_MESSAGE": "showMessage", + "LOG_MESSAGE": "logMessage", + "TELEMETRY": "telemetry", + "DIAGNOSTICS": "diagnostics" + }, + "SERVICE_REQUESTS": { + "SHOW_SELECT_MESSAGE": "showSelectMessage", + "REGISTRATION_REQUEST": "registerDynamicCapability", + "UNREGISTRATION_REQUEST": "unregisterDynamicCapability", + "PROJECT_FOLDERS_REQUEST": "projectFoldersRequest" + }, + "SYNCHRONIZE_EVENTS": { + "DOCUMENT_OPENED": "didOpen", + "DOCUMENT_CHANGED": "didChange", + "DOCUMENT_SAVED": "didSave", + "DOCUMENT_CLOSED": "didClose", + "PROJECT_FOLDERS_CHANGED": "projectRootsChanged" + }, + "FEATURES": { + "CODE_HINTS": "codehints", + "CODE_HINT_INFO": "hintInfo", + "PARAMETER_HINTS": "parameterHints", + "JUMP_TO_DECLARATION": "declaration", + "JUMP_TO_DEFINITION": "definition", + "JUMP_TO_IMPL": "implementation", + "FIND_REFERENCES": "references", + "DOCUMENT_SYMBOLS": "documentSymbols", + "PROJECT_SYMBOLS": "projectSymbols" + } +} diff --git a/src/languageTools/node/RegisterLanguageClientInfo.js b/src/languageTools/node/RegisterLanguageClientInfo.js new file mode 100644 index 00000000000..614be647561 --- /dev/null +++ b/src/languageTools/node/RegisterLanguageClientInfo.js @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports*/ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +"use strict"; + +var domainName = "LanguageClientInfo", + LANGUAGE_CLIENT_RELATIVE_PATH_ARRAY = ["languageTools", "LanguageClient", "LanguageClient"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\", + CompletionItemKind = { + Text: 1, + Method: 2, + Function: 3, + Constructor: 4, + Field: 5, + Variable: 6, + Class: 7, + Interface: 8, + Module: 9, + Property: 10, + Unit: 11, + Value: 12, + Enum: 13, + Keyword: 14, + Snippet: 15, + Color: 16, + File: 17, + Reference: 18, + Folder: 19, + EnumMember: 20, + Constant: 21, + Struct: 22, + Event: 23, + Operator: 24, + TypeParameter: 25 + }, + SymbolKind = { + File: 1, + Module: 2, + Namespace: 3, + Package: 4, + Class: 5, + Method: 6, + Property: 7, + Field: 8, + Constructor: 9, + Enum: 10, + Interface: 11, + Function: 12, + Variable: 13, + Constant: 14, + String: 15, + Number: 16, + Boolean: 17, + Array: 18, + Object: 19, + Key: 20, + Null: 21, + EnumMember: 22, + Struct: 23, + Event: 24, + Operator: 25, + TypeParameter: 26 + }, + defaultBracketsCapabilities = { + //brackets default capabilties + "workspace": { + "workspaceFolders": true, + "symbol": { + "dynamicRegistration": false, + "symbolKind": [ + SymbolKind.File, + SymbolKind.Module, + SymbolKind.Namespace, + SymbolKind.Package, + SymbolKind.Class, + SymbolKind.Method, + SymbolKind.Property, + SymbolKind.Field, + SymbolKind.Constructor, + SymbolKind.Enum, + SymbolKind.Interface, + SymbolKind.Function, + SymbolKind.Variable, + SymbolKind.Constant, + SymbolKind.String, + SymbolKind.Number, + SymbolKind.Boolean, + SymbolKind.Array, + SymbolKind.Object, + SymbolKind.Key, + SymbolKind.Null, + SymbolKind.EnumMember, + SymbolKind.Struct, + SymbolKind.Event, + SymbolKind.Operator, + SymbolKind.TypeParameter + ] + } + }, + "textDocument": { + "synchronization": { + "didSave": true + }, + "completion": { + "dynamicRegistration": false, + "completionItem": { + "deprecatedSupport": true, + "documentationFormat": ["plaintext"], + "preselectSupport": true + }, + "completionItemKind": { + "valueSet": [ + CompletionItemKind.Text, + CompletionItemKind.Method, + CompletionItemKind.Function, + CompletionItemKind.Constructor, + CompletionItemKind.Field, + CompletionItemKind.Variable, + CompletionItemKind.Class, + CompletionItemKind.Interface, + CompletionItemKind.Module, + CompletionItemKind.Property, + CompletionItemKind.Unit, + CompletionItemKind.Value, + CompletionItemKind.Enum, + CompletionItemKind.Keyword, + CompletionItemKind.Snippet, + CompletionItemKind.Color, + CompletionItemKind.File, + CompletionItemKind.Reference, + CompletionItemKind.Folder, + CompletionItemKind.EnumMember, + CompletionItemKind.Constant, + CompletionItemKind.Struct, + CompletionItemKind.Event, + CompletionItemKind.Operator, + CompletionItemKind.TypeParameter + ] + }, + "contextSupport": true + }, + "signatureHelp": { + "dynamicRegistration": false, + "signatureInformation": { + "documentationFormat": ["plaintext"] + } + }, + "references": { + "dynamicRegistration": false + }, + "documentSymbol": { + "dynamicRegistration": false, + "symbolKind": { + "valueSet": [ + SymbolKind.File, + SymbolKind.Module, + SymbolKind.Namespace, + SymbolKind.Package, + SymbolKind.Class, + SymbolKind.Method, + SymbolKind.Property, + SymbolKind.Field, + SymbolKind.Constructor, + SymbolKind.Enum, + SymbolKind.Interface, + SymbolKind.Function, + SymbolKind.Variable, + SymbolKind.Constant, + SymbolKind.String, + SymbolKind.Number, + SymbolKind.Boolean, + SymbolKind.Array, + SymbolKind.Object, + SymbolKind.Key, + SymbolKind.Null, + SymbolKind.EnumMember, + SymbolKind.Struct, + SymbolKind.Event, + SymbolKind.Operator, + SymbolKind.TypeParameter + ] + }, + "hierarchicalDocumentSymbolSupport": false + }, + "definition": { + "dynamicRegistration": false + }, + "declaration": { + "dynamicRegistration": false + }, + "typeDefinition": { + "dynamicRegistration": false + }, + "implementation": { + "dynamicRegistration": false + }, + "publishDiagnostics": { + "relatedInformation": true + } + } + }; + +function syncPreferences(prefs) { + global.LanguageClientInfo = global.LanguageClientInfo || {}; + global.LanguageClientInfo.preferences = prefs || global.LanguageClientInfo.preferences || {}; +} + +function initialize(bracketsSourcePath, toolingInfo) { + var normalizedBracketsSourcePath = bracketsSourcePath.split(BACKWARD_SLASH).join(FORWARD_SLASH), + bracketsSourcePathArray = normalizedBracketsSourcePath.split(FORWARD_SLASH), + languageClientAbsolutePath = bracketsSourcePathArray.concat(LANGUAGE_CLIENT_RELATIVE_PATH_ARRAY).join(FORWARD_SLASH); + + global.LanguageClientInfo = global.LanguageClientInfo || {}; + global.LanguageClientInfo.languageClientPath = languageClientAbsolutePath; + global.LanguageClientInfo.defaultBracketsCapabilities = defaultBracketsCapabilities; + global.LanguageClientInfo.toolingInfo = toolingInfo; + global.LanguageClientInfo.preferences = {}; +} + +function init(domainManager) { + if (!domainManager.hasDomain(domainName)) { + domainManager.registerDomain(domainName, { + major: 0, + minor: 1 + }); + } + + domainManager.registerCommand( + domainName, + "initialize", + initialize, + false, + "Initialize node environment for Language Client Module", + [ + { + name: "bracketsSourcePath", + type: "string", + description: "Absolute path to the brackets source" + }, + { + name: "toolingInfo", + type: "object", + description: "Tooling Info json to be used by Language Client" + } + ], + [] + ); + + domainManager.registerCommand( + domainName, + "syncPreferences", + syncPreferences, + false, + "Sync language tools preferences for Language Client Module", + [ + { + name: "prefs", + type: "object", + description: "Language tools preferences" + } + ], + [] + ); +} + +exports.init = init; diff --git a/src/languageTools/styles/default_provider_style.css b/src/languageTools/styles/default_provider_style.css new file mode 100644 index 00000000000..5090330b28c --- /dev/null +++ b/src/languageTools/styles/default_provider_style.css @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + + +span.brackets-hints-with-type-details { + width: 300px; + display: inline-block; +} + +.brackets-hints-type-details { + color: #a3a3a3 !important; + font-weight: 100; + font-style: italic !important; + margin-right: 5px; + float: right; +} + +.hint-description { + display: none; + color: #d4d4d4; + word-wrap: break-word; + white-space: normal; + box-sizing: border-box; +} + +.hint-doc { + display: none; + padding-right: 10px !important; + color: grey; + word-wrap: break-word; + white-space: normal; + box-sizing: border-box; + float: left; + clear: left; + max-height: 2em; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1em; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.highlight .hint-description { + display: block; + color: #6495ed !important; +} + +.highlight .hint-doc { + display: -webkit-box; +} + +.dark .brackets-hints-type-details { + color: #696969 !important; +} + +.highlight .brackets-hints-type-details { + display: none; +} + +.brackets-hints-keyword { + font-weight: 100; + font-style: italic !important; + margin-right: 5px; + float: right; + color: #6495ed !important; +} + +.brackets-hints .matched-hint { + font-weight: 500; +} + +#function-hint-container-new { + display: none; + + background: #fff; + position: absolute; + z-index: 15; + left: 400px; + top: 40px; + height: auto; + width: auto; + overflow: scroll; + + padding: 1px 6px; + text-align: center; + + border-radius: 3px; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.24); +} + +#function-hint-container-new .function-hint-content-new { + text-align: left; +} + +.brackets-hints .current-parameter { + font-weight: 500; +} + +/* Dark Styles */ + +.dark #function-hint-container-new { + background: #000; + border: 1px solid rgba(255, 255, 255, 0.15); + color: #fff; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.24); +} + +.dark .hint-doc { + color: #ccc; +} + +.dark .hint-description { + color: #ccc; +} diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 594a566764e..074c462c438 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -875,5 +875,8 @@ define({ "NUMBER_WITH_PERCENTAGE" : "{0}%", // Strings for Related Files - "CMD_FIND_RELATED_FILES" : "Find Related Files" + "CMD_FIND_RELATED_FILES" : "Find Related Files", + + //Strings for LanguageTools Preferences + LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" }); diff --git a/tasks/npm-install.js b/tasks/npm-install.js index cad79951927..76434dce5e1 100644 --- a/tasks/npm-install.js +++ b/tasks/npm-install.js @@ -42,9 +42,10 @@ module.exports = function (grunt) { temp.track(); - function runNpmInstall(where, callback) { - grunt.log.writeln("running npm install --production in " + where); - exec('npm install --production', { cwd: './' + where }, function (err, stdout, stderr) { + function runNpmInstall(where, callback, includeDevDependencies) { + var envFlag = includeDevDependencies ? "" : " --production"; + grunt.log.writeln("running npm install" + envFlag + " in " + where); + exec('npm install' + envFlag, { cwd: './' + where }, function (err, stdout, stderr) { if (err) { grunt.log.error(stderr); } else { @@ -71,7 +72,7 @@ module.exports = function (grunt) { grunt.registerTask("npm-install-src", "Install node_modules to the src folder", function () { var _done = this.async(), - dirs = ["src", "src/JSUtils", "src/JSUtils/node"], + dirs = ["src", "src/JSUtils", "src/JSUtils/node", "src/languageTools/LanguageClient"], done = _.after(dirs.length, _done); dirs.forEach(function (dir) { runNpmInstall(dir, function (err) { @@ -99,10 +100,34 @@ module.exports = function (grunt) { }); }); + grunt.registerTask("npm-install-test", "Install node_modules for tests", function () { + var _done = this.async(); + var testDirs = [ + "spec/LanguageTools-test-files" + ]; + testDirs.forEach(function (dir) { + glob("test/" + dir + "/**/package.json", function (err, files) { + if (err) { + grunt.log.error(err); + return _done(false); + } + files = files.filter(function (path) { + return path.indexOf("node_modules") === -1; + }); + var done = _.after(files.length, _done); + files.forEach(function (file) { + runNpmInstall(path.dirname(file), function (err) { + return err ? _done(false) : done(); + }, true); + }); + }); + }); + }); + grunt.registerTask( "npm-install-source", "Install node_modules for src folder and default extensions which have package.json defined", - ["npm-install-src", "copy:thirdparty", "npm-install-extensions"] + ["npm-install-src", "copy:thirdparty", "npm-install-extensions", "npm-install-test"] ); function getNodeModulePackageUrl(extensionName) { diff --git a/test/SpecRunner.js b/test/SpecRunner.js index 6b25937ed24..075c7c48013 100644 --- a/test/SpecRunner.js +++ b/test/SpecRunner.js @@ -102,6 +102,18 @@ define(function (require, exports, module) { require("thirdparty/CodeMirror/addon/mode/overlay"); require("thirdparty/CodeMirror/addon/search/searchcursor"); require("thirdparty/CodeMirror/keymap/sublime"); + + //load Language Tools Module + require("languageTools/PathConverters"); + require("languageTools/LanguageTools"); + require("languageTools/ClientLoader"); + require("languageTools/BracketsToNodeInterface"); + require("languageTools/DefaultProviders"); + require("languageTools/DefaultEventHandlers"); + + //load language features + require("features/ParameterHintsManager"); + require("features/JumpToDefManager"); var selectedSuites, params = new UrlParams(), diff --git a/test/UnitTestSuite.js b/test/UnitTestSuite.js index b32c99f5977..5c9b54d5101 100644 --- a/test/UnitTestSuite.js +++ b/test/UnitTestSuite.js @@ -61,6 +61,7 @@ define(function (require, exports, module) { require("spec/JSONUtils-test"); require("spec/KeyBindingManager-test"); require("spec/LanguageManager-test"); + require("spec/LanguageTools-test"); require("spec/LiveDevelopment-test"); require("spec/LiveDevelopmentMultiBrowser-test"); require("spec/LowLevelFileIO-test"); diff --git a/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js new file mode 100644 index 00000000000..0f42ff886bc --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + clientName = "CommunicationTestClient", + client = null, + modulePath = null, + getPort = require("get-port"), + relativeLSPathArray = ["..", "..", "server", "lsp-test-server", "main.js"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\", + defaultPort = 3000; + +function getServerOptionsForSocket() { + return new Promise(function (resolve, reject) { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + getPort({ + port: defaultPort + }) + .then(function (port) { + + var serverOptions = { + module: serverPath, + communication: { + type: "socket", + port: port + } + }; + resolve(serverOptions); + }) + .catch(reject); + + }); +} + +function getServerOptions(type) { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + serverPath = path.resolve(serverPath); + + var serverOptions = { + module: serverPath, + communication: type + }; + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + if (!params || !params.communicationType) { + return Promise.reject("Can't start server because no communication type provided"); + } + + var cType = params.communicationType, + options = { + serverOptions: getServerOptions(cType) + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function setOptionsForSocket() { + return new Promise(function (resolve, reject) { + getServerOptionsForSocket() + .then(function (serverOptions) { + var options = { + serverOptions: serverOptions + }; + client.setOptions(options); + + resolve(); + }).catch(reject); + }); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); + client.addOnRequestHandler('setOptionsForSocket', setOptionsForSocket); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js new file mode 100644 index 00000000000..52c8ce0d28c --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "CommunicationTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(retval.resolve); + + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json new file mode 100644 index 00000000000..cc25d2ac62d --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "get-port": "^4.2.0" + } +} diff --git a/test/spec/LanguageTools-test-files/clients/FeatureClient/client.js b/test/spec/LanguageTools-test-files/clients/FeatureClient/client.js new file mode 100644 index 00000000000..95050e7e0b8 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/FeatureClient/client.js @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + clientName = "FeatureClient", + client = null, + modulePath = null, + relativeLSPathArray = ["..", "..", "server", "lsp-test-server", "main.js"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\"; + +function getServerOptions() { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + serverPath = path.resolve(serverPath); + + var serverOptions = { + module: serverPath //node should fork this + }; + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + var options = { + serverOptions: getServerOptions() + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/FeatureClient/main.js b/test/spec/LanguageTools-test-files/clients/FeatureClient/main.js new file mode 100644 index 00000000000..8243d14fd5f --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/FeatureClient/main.js @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "FeatureClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(function () { + return client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions" + }); + }).then(function () { + retval.resolve(); + }); + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js new file mode 100644 index 00000000000..15ae4c8de3d --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + clientName = "InterfaceTestClient", + client = null; + +function notificationMethod(params) { + switch (params.action) { + case 'acknowledgement': + { + client._notifyBrackets({ + type: "acknowledge", + params: { + acknowledgement: true, + clientName: clientName + } + }); + break; + } + case 'nodeSyncRequest': + { + var syncRequest = client._requestBrackets({ + type: "nodeSyncRequest", + params: { + syncRequest: true, + clientName: clientName + } + }); + + syncRequest.then(function (value) { + client._notifyBrackets({ + type: "validateSyncRequest", + params: { + syncRequestResult: value, + clientName: clientName + } + }); + }); + break; + } + case 'nodeAsyncRequestWhichResolves': + { + var asyncRequestS = client._requestBrackets({ + type: "nodeAsyncRequestWhichResolves", + params: { + asyncRequest: true, + clientName: clientName + } + }); + + asyncRequestS.then(function (value) { + client._notifyBrackets({ + type: "validateAsyncSuccess", + params: { + asyncRequestResult: value, + clientName: clientName + } + }); + }); + break; + } + case 'nodeAsyncRequestWhichFails': + { + var asyncRequestE = client._requestBrackets({ + type: "nodeAsyncRequestWhichFails", + params: { + asyncRequest: true, + clientName: clientName + } + }); + + asyncRequestE.catch(function (value) { + client._notifyBrackets({ + type: "validateAsyncFail", + params: { + asyncRequestError: value, + clientName: clientName + } + }); + }); + break; + } + } +} + +function requestMethod(params) { + switch (params.action) { + case 'resolve': + { + return Promise.resolve("resolved"); + } + case 'reject': + { + return Promise.reject("rejected"); + } + } +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnNotificationHandler("notificationMethod", notificationMethod); + client.addOnRequestHandler('requestMethod', requestMethod); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js new file mode 100644 index 00000000000..5889e5a3b37 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "InterfaceTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + retval.resolve(); + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js new file mode 100644 index 00000000000..9dd77848740 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ + +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + clientName = "LoadSimpleClient", + client = null; + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js new file mode 100644 index 00000000000..c4160a98cfe --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "LoadSimpleClient", + clientPromise = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(retval.resolve, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js new file mode 100644 index 00000000000..2f5c22c2f64 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + clientName = "ModuleTestClient", + client = null, + modulePath = null, + relativeLSPathArray = ["..", "..", "server", "lsp-test-server", "main.js"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\"; + +function getServerOptions() { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + serverPath = path.resolve(serverPath); + + var serverOptions = { + module: serverPath //node should fork this + }; + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + var options = { + serverOptions: getServerOptions() + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js new file mode 100644 index 00000000000..b3507f9d887 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "ModuleTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(function () { + return client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions" + }); + }).then(function () { + retval.resolve(); + }); + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js new file mode 100644 index 00000000000..d038105cae0 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ + +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + cp = require("child_process"), + clientName = "OptionsTestClient", + client = null, + modulePath = null, + relativeLSPathArray = ["..", "..", "server", "lsp-test-server"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\"; + +function getServerOptions(type) { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + var newEnv = process.env; + newEnv.CUSTOMENVVARIABLE = "ANYTHING"; + + serverPath = path.resolve(serverPath); + var serverOptions = null; + + switch (type) { + case 'runtime': + { + // [runtime] [execArgs] [module] [args (with communication args)] (with options[env, cwd]) + serverOptions = { + runtime: process.execPath, //Path to node but could be anything, like php or perl + module: "main.js", + args: [ + "--server-args" //module args + ], //Arguments to process + options: { + cwd: serverPath, //The current directory where main.js is located + env: newEnv, //The process will be started CUSTOMENVVARIABLE in its environment + execArgv: [ + "--no-warnings", + "--no-deprecation" //runtime executable args + ] + }, + communication: "ipc" + }; + break; + } + case 'function': + { + serverOptions = function () { + return new Promise(function (resolve, reject) { + var serverProcess = cp.spawn(process.execPath, [ + "main.js", + "--stdio" //Have to add communication args manually + ], { + cwd: serverPath + }); + + if (serverProcess && serverProcess.pid) { + resolve({ + process: serverProcess + }); + } else { + reject("Couldn't create server process"); + } + }); + }; + break; + } + case 'command': + { + // [command] [args] (with options[env, cwd]) + serverOptions = { + command: process.execPath, //Path to executable, mostly runtime + args: [ + "--no-warnings", + "--no-deprecation", + "main.js", + "--stdio", //Have to add communication args manually + "--server-args" + ], //Arguments to process, ORDER WILL MATTER + options: { + cwd: serverPath, + env: newEnv //The process will be started CUSTOMENVVARIABLE in its environment + } + }; + break; + } + } + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + if (!params || !params.optionsType) { + return Promise.reject("Can't start server because no options type provided"); + } + + var oType = params.optionsType, + options = { + serverOptions: getServerOptions(oType) + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js new file mode 100644 index 00000000000..67610b9f0ed --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "OptionsTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(retval.resolve); + + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/project/sample1.txt b/test/spec/LanguageTools-test-files/project/sample1.txt new file mode 100644 index 00000000000..8de75dcb4d7 --- /dev/null +++ b/test/spec/LanguageTools-test-files/project/sample1.txt @@ -0,0 +1 @@ +This has some text. \ No newline at end of file diff --git a/test/spec/LanguageTools-test-files/project/sample2.txt b/test/spec/LanguageTools-test-files/project/sample2.txt new file mode 100644 index 00000000000..9289cdf2601 --- /dev/null +++ b/test/spec/LanguageTools-test-files/project/sample2.txt @@ -0,0 +1 @@ +This has error text. \ No newline at end of file diff --git a/test/spec/LanguageTools-test-files/server/lsp-test-server/main.js b/test/spec/LanguageTools-test-files/server/lsp-test-server/main.js new file mode 100644 index 00000000000..2e0358eab1d --- /dev/null +++ b/test/spec/LanguageTools-test-files/server/lsp-test-server/main.js @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +'use strict'; + +var vls = require("vscode-languageserver"), + connection = vls.createConnection(vls.ProposedFeatures.all); + +connection.onInitialize(function (params) { + return { + capabilities: { + textDocumentSync: 1, + completionProvider: { + resolveProvider: true, + triggerCharacters: [ + '=', + ' ', + '$', + '-', + '&' + ] + }, + definitionProvider: true, + signatureHelpProvider: { + triggerCharacters: [ + '-', + '[', + ',', + ' ', + '=' + ] + }, + "workspaceSymbolProvider": "true", + "documentSymbolProvider": "true", + "referencesProvider": "true" + } + }; +}); + +connection.onInitialized(function () { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.InitializedNotification.type._method + } + }); + + connection.workspace.onDidChangeWorkspaceFolders(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidChangeWorkspaceFoldersNotification.type._method, + params: params + } + }); + }); +}); + +connection.onCompletion(function (params) { + return { + received: { + type: vls.CompletionRequest.type._method, + params: params + } + }; +}); + +connection.onSignatureHelp(function (params) { + return { + received: { + type: vls.SignatureHelpRequest.type._method, + params: params + } + }; +}); + +connection.onCompletionResolve(function (params) { + return { + received: { + type: vls.CompletionResolveRequest.type._method, + params: params + } + }; +}); + +connection.onDefinition(function (params) { + return { + received: { + type: vls.DefinitionRequest.type._method, + params: params + } + }; +}); + +connection.onDeclaration(function (params) { + return { + received: { + type: vls.DeclarationRequest.type._method, + params: params + } + }; +}); + +connection.onImplementation(function (params) { + return { + received: { + type: vls.ImplementationRequest.type._method, + params: params + } + }; +}); + +connection.onDocumentSymbol(function (params) { + return { + received: { + type: vls.DocumentSymbolRequest.type._method, + params: params + } + }; +}); + +connection.onWorkspaceSymbol(function (params) { + return { + received: { + type: vls.WorkspaceSymbolRequest.type._method, + params: params + } + }; +}); + +connection.onDidOpenTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidOpenTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onDidChangeTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidChangeTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onDidCloseTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidCloseTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onDidSaveTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidSaveTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onNotification(function (type, params) { + switch (type) { + case "custom/triggerDiagnostics": + { + connection.sendDiagnostics({ + received: { + type: type, + params: params + } + }); + break; + } + case "custom/getNotification": + { + connection.sendNotification("custom/serverNotification", { + received: { + type: type, + params: params + } + }); + break; + } + case "custom/getRequest": + { + connection.sendRequest("custom/serverRequest", { + received: { + type: type, + params: params + } + }).then(function (resolveResponse) { + connection.sendNotification("custom/requestSuccessNotification", { + received: { + type: "custom/requestSuccessNotification", + params: resolveResponse + } + }); + }).catch(function (rejectResponse) { + connection.sendNotification("custom/requestFailedNotification", { + received: { + type: "custom/requestFailedNotification", + params: rejectResponse + } + }); + }); + break; + } + default: + { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: type, + params: params + } + }); + } + } +}); + +connection.onRequest(function (type, params) { + return { + received: { + type: type, + params: params + } + }; +}); + +// Listen on the connection +connection.listen(); diff --git a/test/spec/LanguageTools-test-files/server/lsp-test-server/package.json b/test/spec/LanguageTools-test-files/server/lsp-test-server/package.json new file mode 100644 index 00000000000..17b9af0423e --- /dev/null +++ b/test/spec/LanguageTools-test-files/server/lsp-test-server/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "vscode-languageserver": "^5.3.0-next.1" + } +} diff --git a/test/spec/LanguageTools-test.js b/test/spec/LanguageTools-test.js new file mode 100644 index 00000000000..0b80a3db32d --- /dev/null +++ b/test/spec/LanguageTools-test.js @@ -0,0 +1,1599 @@ +/* + * Copyright (c) 2019 - present Adobe. 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 regexp: true */ +/*global describe, it, expect, spyOn, runs, waitsForDone, waitsForFail, afterEach */ +/*eslint indent: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + 'use strict'; + + // Load dependent modules + var ExtensionLoader = require("utils/ExtensionLoader"), + SpecRunnerUtils = require("spec/SpecRunnerUtils"), + LanguageClientWrapper = require("languageTools/LanguageClientWrapper"), + LanguageTools = require("languageTools/LanguageTools"), + EventDispatcher = require("utils/EventDispatcher"), + ToolingInfo = JSON.parse(brackets.getModule("text!languageTools/ToolingInfo.json")); + + var testPath = SpecRunnerUtils.getTestPath("/spec/LanguageTools-test-files"), + serverResponse = { + capabilities: { + textDocumentSync: 1, + completionProvider: { + resolveProvider: true, + triggerCharacters: [ + '=', + ' ', + '$', + '-', + '&' + ] + }, + definitionProvider: true, + signatureHelpProvider: { + triggerCharacters: [ + '-', + '[', + ',', + ' ', + '=' + ] + }, + "workspaceSymbolProvider": "true", + "documentSymbolProvider": "true", + "referencesProvider": "true" + } + }; + + describe("LanguageTools", function () { + function loadClient(name) { + var config = { + baseUrl: testPath + "/clients/" + name + }; + + return ExtensionLoader.loadExtension(name, config, "main"); + } + + function getExtensionFromContext(name) { + var extensionContext = brackets.libRequire.s.contexts[name]; + + return extensionContext && extensionContext.defined && extensionContext.defined.main; + } + + it("should load a simple test client extension", function () { + var promise, + consoleErrors = []; + + runs(function () { + var originalConsoleErrorFn = console.error; + spyOn(console, "error").andCallFake(function () { + originalConsoleErrorFn.apply(console, arguments); + + if (typeof arguments[0] === "string" && + arguments[0].includes("Error loading domain \"LoadSimpleClient\"")) { + consoleErrors.push(Array.prototype.join.call(arguments)); + } + }); + + promise = loadClient("LoadSimpleClient"); + + waitsForDone(promise, "loadClient"); + }); + + runs(function () { + expect(consoleErrors).toEqual([]); + expect(promise.state()).toBe("resolved"); + }); + }); + + describe("Brackets & Node Communication", function () { + var intefacePromise, + extension, + client; + + it("should load the interface client extension", function () { + runs(function () { + intefacePromise = loadClient("InterfaceTestClient"); + intefacePromise.done(function () { + extension = getExtensionFromContext("InterfaceTestClient"); + client = extension.getClient(); + }); + + waitsForDone(intefacePromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("InterfaceTestClient"); + }); + }); + + it("should receive acknowledgement notification after sending notification to node", function () { + var notificationStatus = false; + + function notifyWithPromise() { + var retval = $.Deferred(); + + client._addOnNotificationHandler("acknowledge", function (params) { + if (params.clientName === "InterfaceTestClient" && params.acknowledgement) { + notificationStatus = true; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "acknowledgement" + } + }); + + return retval; + } + + runs(function () { + var notificationPromise = notifyWithPromise(); + + waitsForDone(notificationPromise, "NotificationInterface"); + }); + + runs(function () { + expect(notificationStatus).toBe(true); + }); + }); + + it("should send request to node which should resolve", function () { + var result = null; + + function requestWithPromise() { + return client.sendCustomRequest({ + messageType: "brackets", + type: "requestMethod", + params: { + action: "resolve" + } + }); + } + + runs(function () { + var requestPromise = requestWithPromise(); + requestPromise.done(function (returnVal) { + result = returnVal; + }); + + waitsForDone(requestPromise, "RequestInterface"); + }); + + runs(function () { + expect(result).toBe("resolved"); + }); + }); + + it("should send request to node which should reject", function () { + var result = null; + + function requestWithPromise() { + return client.sendCustomRequest({ + messageType: "brackets", + type: "requestMethod", + params: { + action: "reject" + } + }); + } + + runs(function () { + var requestPromise = requestWithPromise(); + requestPromise.fail(function (returnVal) { + result = returnVal; + }); + + waitsForFail(requestPromise, "RequestInterface"); + }); + + runs(function () { + expect(result).toBe("rejected"); + }); + }); + + it("should handle sync request from node side", function () { + var requestResult = null; + + function nodeRequestWithPromise() { + var retval = $.Deferred(); + + client._addOnRequestHandler("nodeSyncRequest", function (params) { + if (params.clientName === "InterfaceTestClient" && params.syncRequest) { + //We return value directly since it is a sync request + return "success"; + } + }); + + //trigger request from node side + client._addOnNotificationHandler("validateSyncRequest", function (params) { + if (params.clientName === "InterfaceTestClient" && params.syncRequestResult) { + requestResult = params.syncRequestResult; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "nodeSyncRequest" + } + }); + + return retval; + } + + runs(function () { + var nodeRequestPromise = nodeRequestWithPromise(); + + waitsForDone(nodeRequestPromise, "NodeRequestInterface"); + }); + + runs(function () { + expect(requestResult).toEqual("success"); + }); + }); + + it("should handle async request from node side which is resolved", function () { + var requestResult = null; + + function nodeRequestWithPromise() { + var retval = $.Deferred(); + + client._addOnRequestHandler("nodeAsyncRequestWhichResolves", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequest) { + //We return promise which can be resolved in async + return $.Deferred().resolve("success"); + } + }); + + //trigger request from node side + client._addOnNotificationHandler("validateAsyncSuccess", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequestResult) { + requestResult = params.asyncRequestResult; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "nodeAsyncRequestWhichResolves" + } + }); + + return retval; + } + + runs(function () { + var nodeRequestPromise = nodeRequestWithPromise(); + + waitsForDone(nodeRequestPromise, "NodeRequestInterface"); + }); + + runs(function () { + expect(requestResult).toEqual("success"); + }); + }); + + it("should handle async request from node side which fails", function () { + var requestResult = null; + + function nodeRequestWithPromise() { + var retval = $.Deferred(); + + client._addOnRequestHandler("nodeAsyncRequestWhichFails", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequest) { + //We return promise which can be resolved in async + return $.Deferred().reject("error"); + } + }); + + //trigger request from node side + client._addOnNotificationHandler("validateAsyncFail", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequestError) { + requestResult = params.asyncRequestError; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "nodeAsyncRequestWhichFails" + } + }); + + return retval; + } + + runs(function () { + var nodeRequestPromise = nodeRequestWithPromise(); + + waitsForDone(nodeRequestPromise, "NodeRequestInterface"); + }); + + runs(function () { + expect(requestResult).toEqual("error"); + }); + }); + }); + + describe("Client Start and Stop Tests", function () { + var projectPath = testPath + "/project", + optionsPromise, + extension, + client = null; + + it("should start a simple module based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("ModuleTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("ModuleTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("ModuleTestClient"); + + startPromise = client.start({ + rootPath: projectPath + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should stop a simple module based client", function () { + var restartPromise, + restartStatus = false; + + runs(function () { + if (client) { + restartPromise = client.stop().done(function () { + return client.start({ + rootPath: projectPath + }); + }); + restartPromise.done(function () { + restartStatus = true; + }); + } + + waitsForDone(restartPromise, "RestartClient"); + }); + + runs(function () { + expect(restartStatus).toBe(true); + }); + }); + + + it("should stop a simple module based client", function () { + var stopPromise, + stopStatus = false; + + runs(function () { + if (client) { + stopPromise = client.stop(); + stopPromise.done(function () { + stopStatus = true; + client = null; + }); + } + + waitsForDone(stopPromise, "StopClient"); + }); + + runs(function () { + expect(stopStatus).toBe(true); + }); + }); + }); + + describe("Language Server Spawn Schemes", function () { + var projectPath = testPath + "/project", + optionsPromise, + extension, + client = null; + + afterEach(function () { + var stopPromise, + stopStatus = false; + + runs(function () { + if (client) { + stopPromise = client.stop(); + stopPromise.done(function () { + stopStatus = true; + client = null; + }); + } else { + stopStatus = true; + } + + waitsForDone(stopPromise, "StopClient"); + }); + + runs(function () { + expect(stopStatus).toBe(true); + }); + }); + + it("should start a simple module based client with node-ipc", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + communicationType: "ipc" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple module based client with stdio", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + communicationType: "stdio" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple module based client with pipe", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + communicationType: "pipe" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple module based client with socket", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptionsForSocket" + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple runtime based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("OptionsTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("OptionsTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("OptionsTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + optionsType: "runtime" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple function based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("OptionsTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("OptionsTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("OptionsTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + optionsType: "function" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple command based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("OptionsTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("OptionsTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("OptionsTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + optionsType: "command" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + }); + + describe("Parameter validation for client based communication", function () { + var requestValidator = LanguageClientWrapper.validateRequestParams, + notificationValidator = LanguageClientWrapper.validateNotificationParams; + + var paramTemplateA = { + rootPath: "somePath" + }; + + var paramTemplateB = { + filePath: "somePath", + cursorPos: { + line: 1, + ch: 1 + } + }; + + var paramTemplateC = { + filePath: "somePath" + }; + + var paramTemplateD = { + filePath: "something", + fileContent: "something", + languageId: "something" + }; + + var paramTemplateE = { + filePath: "something", + fileContent: "something" + }; + + var paramTemplateF = { + foldersAdded: ["added"], + foldersRemoved: ["removed"] + }; + + it("should validate the params for request: client.start", function () { + var params = Object.assign({}, paramTemplateA), + retval = requestValidator(ToolingInfo.LANGUAGE_SERVICE.START, params); + + var params2 = Object.assign({}, paramTemplateA); + params2["capabilities"] = { + feature: true + }; + var retval2 = requestValidator(ToolingInfo.LANGUAGE_SERVICE.START, params2); + + expect(retval).toEqual({ + rootPath: "somePath", + capabilities: false + }); + + expect(retval2).toEqual({ + rootPath: "somePath", + capabilities: { + feature: true + } + }); + }); + + it("should invalidate the params for request: client.start", function () { + var retval = requestValidator(ToolingInfo.LANGUAGE_SERVICE.START, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.{requestHints, requestParameterHints, gotoDefinition}", function () { + var params = Object.assign({}, paramTemplateB), + retval = requestValidator(ToolingInfo.FEATURES.CODE_HINTS, params); + + expect(retval).toEqual(paramTemplateB); + }); + + it("should invalidate the params for request: client.{requestHints, requestParameterHints, gotoDefinition}", function () { + var retval = requestValidator(ToolingInfo.FEATURES.CODE_HINTS, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.findReferences", function () { + var params = Object.assign({}, paramTemplateB), + retval = requestValidator(ToolingInfo.FEATURES.FIND_REFERENCES, params); + + var result = Object.assign({}, paramTemplateB); + result["includeDeclaration"] = false; + + expect(retval).toEqual(result); + }); + + it("should invalidate the params for request: client.findReferences", function () { + var retval = requestValidator(ToolingInfo.FEATURES.FIND_REFERENCES, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.requestSymbolsForDocument", function () { + var params = Object.assign({}, paramTemplateC), + retval = requestValidator(ToolingInfo.FEATURES.DOCUMENT_SYMBOLS, params); + + expect(retval).toEqual(paramTemplateC); + }); + + it("should invalidate the params for request: client.requestSymbolsForDocument", function () { + var retval = requestValidator(ToolingInfo.FEATURES.DOCUMENT_SYMBOLS, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.requestSymbolsForWorkspace", function () { + var params = Object.assign({}, { + query: 'a' + }), + retval = requestValidator(ToolingInfo.FEATURES.PROJECT_SYMBOLS, params); + + expect(retval).toEqual({ + query: 'a' + }); + }); + + it("should invalidate the params for request: client.requestSymbolsForWorkspace", function () { + var retval = requestValidator(ToolingInfo.FEATURES.PROJECT_SYMBOLS, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.notifyTextDocumentOpened", function () { + var params = Object.assign({}, paramTemplateD), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED, params); + + expect(retval).toEqual(paramTemplateD); + }); + + it("should invalidate the params for notification: client.notifyTextDocumentOpened", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.notifyTextDocumentChanged", function () { + var params = Object.assign({}, paramTemplateE), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED, params); + + expect(retval).toEqual(paramTemplateE); + }); + + it("should invalidate the params for notification: client.notifyTextDocumentChanged", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.{notifyTextDocumentClosed, notifyTextDocumentSave}", function () { + var params = Object.assign({}, paramTemplateC), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED, params); + + expect(retval).toEqual(paramTemplateC); + }); + + it("should invalidate the params for notification: client.{notifyTextDocumentClosed, notifyTextDocumentSave}", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.notifyProjectRootsChanged", function () { + var params = Object.assign({}, paramTemplateF), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED, params); + + expect(retval).toEqual(paramTemplateF); + }); + + it("should invalidate the params for notification: client.notifyProjectRootsChanged", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED, {}); + + expect(retval).toBeNull(); + }); + + it("should passthrough the params for request: client.sendCustomRequest", function () { + var params = Object.assign({}, { + a: 1, + b: 2 + }), + retval = requestValidator(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST, params); + + expect(retval).toEqual({ + a: 1, + b: 2 + }); + }); + + it("should passthrough the params for notification: client.sendCustomNotification", function () { + var params = Object.assign({}, { + a: 1, + b: 2 + }), + retval = notificationValidator(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION, params); + + expect(retval).toEqual({ + a: 1, + b: 2 + }); + }); + + it("should passthrough the params for any request if format is 'lsp'", function () { + var params = Object.assign({}, { + format: 'lsp', + a: 1, + b: 2 + }), + retval = requestValidator("AnyType", params); + + expect(retval).toEqual({ + format: 'lsp', + a: 1, + b: 2 + }); + }); + + it("should passthrough the params for any notification if format is 'lsp'", function () { + var params = Object.assign({}, { + format: 'lsp', + a: 1, + b: 2 + }), + retval = notificationValidator("AnyType", params); + + expect(retval).toEqual({ + format: 'lsp', + a: 1, + b: 2 + }); + }); + }); + + describe("Test LSP Request and Notifications", function () { + var projectPath = testPath + "/project", + featurePromise, + extension, + client = null, + docPath1 = projectPath + "/sample1.txt", + docPath2 = projectPath + "/sample2.txt", + pos = { + line: 1, + ch: 2 + }, + fileContent = "some content", + languageId = "unknown"; + + function createPromiseForNotification(type) { + var promise = $.Deferred(); + + switch (type) { + case "textDocument/publishDiagnostics": { + client.addOnCodeInspection(function (params) { + promise.resolve(params); + }); + break; + } + case "custom/serverNotification": + case "custom/requestSuccessNotification": + case "custom/requestFailedNotification": + { + client.onCustomNotification(type, function (params) { + promise.resolve(params); + }); + break; + } + default: { + client.addOnLogMessage(function (params) { + if (params.received && params.received.type && + params.received.type === type) { + promise.resolve(params); + } + }); + } + } + + return promise; + } + + it("should successfully start client", function () { + var startResult = false, + startPromise; + + runs(function () { + featurePromise = loadClient("FeatureClient"); + featurePromise.done(function () { + extension = getExtensionFromContext("FeatureClient"); + client = extension.getClient(); + }); + + waitsForDone(featurePromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("FeatureClient"); + + client.onDynamicCapabilityRegistration(function () { + return $.Deferred().resolve(); + }); + + client.onDynamicCapabilityUnregistration(function () { + return $.Deferred().resolve(); + }); + + startPromise = client.start({ + rootPath: projectPath + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should successfully requestHints with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestHints({ + filePath: docPath1, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully passthrough params with lsp format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestHints({ + format: 'lsp', + textDocument: { + uri: 'file:///somepath/project/sample1.txt' + }, + position: { + line: 1, + character: 2 + } + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse).toEqual({ + received: { + type: 'textDocument/completion', + params: { + textDocument: { + uri: 'file:///somepath/project/sample1.txt' + }, + position: { + line: 1, + character: 2 + } + } + } + }); + }); + }); + + it("should successfully getAdditionalInfoForHint", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.getAdditionalInfoForHint({ + hintItem: true + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse).toEqual({ + received: { + type: 'completionItem/resolve', + params: { + hintItem: true + } + } + }); + }); + }); + + it("should successfully requestParameterHints with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestParameterHints({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully gotoDefinition with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.gotoDefinition({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully gotoImplementation with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.gotoImplementation({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully gotoDeclaration with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.gotoDeclaration({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully findReferences with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.findReferences({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully requestSymbolsForDocument with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestSymbolsForDocument({ + filePath: docPath2 + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully requestSymbolsForWorkspace", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestSymbolsForWorkspace({ + query: "s" + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully sendCustomRequest to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.sendCustomRequest({ + type: "custom/serverRequest", + params: { + anyParam: true + } + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentOpened to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didOpen"); + client.notifyTextDocumentOpened({ + languageId: languageId, + filePath: docPath1, + fileContent: fileContent + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentClosed to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didClose"); + client.notifyTextDocumentClosed({ + filePath: docPath1 + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentSave to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didSave"); + client.notifyTextDocumentSave({ + filePath: docPath2 + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentChanged to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didChange"); + client.notifyTextDocumentChanged({ + filePath: docPath2, + fileContent: fileContent + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyProjectRootsChanged to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("workspace/didChangeWorkspaceFolders"); + client.notifyProjectRootsChanged({ + foldersAdded: ["path1", "path2"], + foldersRemoved: ["path3"] + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully get send custom notification to trigger diagnostics from server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/publishDiagnostics"); + client.sendCustomNotification({ + type: "custom/triggerDiagnostics" + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully create a custom event trigger for server notification", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + EventDispatcher.makeEventDispatcher(exports); + LanguageTools.listenToCustomEvent(exports, "triggerDiagnostics"); + client.addOnCustomEventHandler("triggerDiagnostics", function () { + client.sendCustomNotification({ + type: "custom/triggerDiagnostics" + }); + }); + requestPromise = createPromiseForNotification("textDocument/publishDiagnostics"); + exports.trigger("triggerDiagnostics"); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully handle a custom server notification", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + + requestPromise = createPromiseForNotification("custom/serverNotification"); + client.sendCustomNotification({ + type: "custom/getNotification" + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully handle a custom server request on resolve", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + + requestPromise = createPromiseForNotification("custom/requestSuccessNotification"); + client.onCustomRequest("custom/serverRequest", function (params) { + return $.Deferred().resolve(params); + }); + + client.sendCustomNotification({ + type: "custom/getRequest" + }); + + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully handle a custom server request on reject", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + + requestPromise = createPromiseForNotification("custom/requestFailedNotification"); + client.onCustomRequest("custom/serverRequest", function (params) { + return $.Deferred().reject(params); + }); + + client.sendCustomNotification({ + type: "custom/getRequest" + }); + + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully stop client", function () { + var stopPromise, + stopStatus = false; + + runs(function () { + if (client) { + stopPromise = client.stop(); + stopPromise.done(function () { + stopStatus = true; + client = null; + }); + } + + waitsForDone(stopPromise, "StopClient"); + }); + + runs(function () { + expect(stopStatus).toBe(true); + }); + }); + }); + }); +}); From e7eccb82a1e88ad49b4f2606e32db514dc56598f Mon Sep 17 00:00:00 2001 From: Subhash Jha Date: Tue, 2 Apr 2019 15:05:43 +0530 Subject: [PATCH 054/149] Language Server Protocol Support for Brackets (#14606) * LSP Initial set of changes * Adding comments and a bit of cleanup * Adding php client for lsp * further cleanup * removing dependency on HintUtils * removing phpClient extension from this branch * Cleanup * fixing eslint errors * Refactoring code- Removing dependency on JSUtils ANd adding basic structure for client capabilities * Bug Fix: too many listeners were getting attached to node process + code cleanup * putting null check and settign capabilities to default values * reinitializing server on workspace change and moving out capabilities from client code * cleanup * First cut for LSP support in Brackets * First cut for LSP support in Brackets * Adding client infrastructure * Adding client infrastructure * Adding handlers on Language Client Proxy, fixing eslint errors * Adding handlers on Language Client Proxy, fixing eslint errors * Fixing protocol adapter * Fixing protocol adapter * Fix typo * Fix typo * Removing older implementation * Removing older implementation * Added error checks to the auto update mechanism. So in case the auto update mechansim fails, we will now give chance to the default update process Handler to handle the update mechanism (Which is essentially taking the user to brackets.io). (#14605) * First cut for LSP support in Brackets * First cut for LSP support in Brackets * Adding client infrastructure * Adding client infrastructure * Adding handlers on Language Client Proxy, fixing eslint errors * Adding handlers on Language Client Proxy, fixing eslint errors * Fixing protocol adapter * Fixing protocol adapter * Fix typo * Fix typo * Removing older implementation * Removing older implementation * Removing custom comments * Removing custom comments * Fixing Typo * Fixing Typo * Add missing Params in function call * Add missing Params in function call * Correcting message type, handlers * Correcting message type, handlers * Minor correction on active project change * Minor correction on active project change * Correcting the message format for didChange * Correcting the message format for didChange * Changing custom notification and request handlers, correcting typo, adding catch block for Connection * Changing custom notification and request handlers, correcting typo, adding catch block for Connection * Stop Creation of Multiple Language Servers * Stop Creation of Multiple Language Servers * Make Language Client Generic, address review comments * Make Language Client Generic, address review comments * Correcting param descriptions * Correcting param descriptions * Modifying events handling logic for Language Client, add formatting option for communication params * Modifying events handling logic for Language Client, add formatting option for communication params * Add handlers for node side * Add handlers for node side * Removing explicit param creation, substituting with appropriate checks * Removing explicit param creation, substituting with appropriate checks * Fixing lint errors in MessageHandler.js * Fixing lint errors in MessageHandler.js * Messaging related cleanup * Messaging related cleanup * Adding default providers and feature managers * Adding default providers and feature managers * Adding banner and fixing lint error * Adding banner and fixing lint error * fix spacing issue * fix spacing issue * Fix spacing issues * Fix spacing issues * Add filetype checks for all events, minor server info corrections * Add filetype checks for all events, minor server info corrections * Handling Reload with Extension Scenario, minor JumpToDef provider fix * Handling Reload with Extension Scenario, minor JumpToDef provider fix * Correcting Typo * Correcting Typo * Adding bug fixes * Adding bug fixes * Adding bug fixes 2 * Adding bug fixes 2 * Addressing Review: Fixing minor typo * Addressing Review: Fixing minor typo * Minor bug fixes, functionality enhancements * Minor bug fixes, functionality enhancements * Adding tests for Language Server Support: first cut * Adding tests for Language Server Support: first cut * Adding banner, fixing lint errors * Adding banner, fixing lint errors * Adding dependency related tasks * Adding dependency related tasks * Fixing npm environment string * Fixing npm environment string * Changing handler name * Changing handler name * Changing file name to ClientLoader * Changing file name to ClientLoader * Changing variable name appropriately * Changing variable name appropriately * Grunt related changes for build * Grunt related changes for build * Adding additional requests and notifications for handling various scenarios * Adding additional requests and notifications for handling various scenarios * Adding Path Converter Utilities * Adding Path Converter Utilities * Changing Ternary operator to OR operater * Changing Ternary operator to OR operater * Addressing review comments * Addressing review comments * Removing the handler for editor change, will be handled explicitely * Removing the handler for editor change, will be handled explicitely * Patching JavaScriptCodeHints * Patching JavaScriptCodeHints * Preferences infra for LanguageTools * Preferences infra for LanguageTools * Fixing JS ParameterHints * Fixing JS ParameterHints * Fixing Default Parameter Hints Provider * Fixing Default Parameter Hints Provider * Fixing Path Converters * Fixing Path Converters * Fixing Lint in PathConverters * Fixing Lint in PathConverters * Retaining Posix Path on Win * Retaining Posix Path on Win * Fixing lint errors * Fixing lint errors * Fixing Node side Utils * Fixing Node side Utils * Fixing Promise related Issues * Fixing Promise related Issues * Set Server Capability in Start call * Set Server Capability in Start call * Review Comments & Bug Fixes * Review Comments & Bug Fixes * Addressing Review Comments * Addressing Review Comments * Fixing Lint * Fixing Lint Co-authored-by: Shubham Yadav --- .eslintrc.js | 10 + Gruntfile.js | 3 + src/brackets.js | 13 + src/command/Commands.js | 2 +- src/editor/EditorManager.js | 76 - .../ParameterHintManager.js | 445 ----- .../ParameterHintTemplate.html | 4 - .../ParameterHintsProvider.js | 229 +++ .../default/JavaScriptCodeHints/keyboard.json | 11 - .../default/JavaScriptCodeHints/main.js | 69 +- .../default/JavaScriptCodeHints/unittests.js | 79 +- .../default/JavaScriptQuickEdit/unittests.js | 64 +- src/features/JumpToDefManager.js | 89 + src/features/ParameterHintsManager.js | 416 +++++ src/features/PriorityBasedRegistration.js | 138 ++ src/htmlContent/parameter-hint-template.html | 4 + src/languageTools/BracketsToNodeInterface.js | 121 ++ src/languageTools/ClientLoader.js | 128 ++ src/languageTools/DefaultEventHandlers.js | 193 ++ src/languageTools/DefaultProviders.js | 384 ++++ .../LanguageClient/Connection.js | 134 ++ .../LanguageClient/LanguageClient.js | 232 +++ .../LanguageClient/NodeToBracketsInterface.js | 213 +++ .../LanguageClient/ProtocolAdapter.js | 398 ++++ .../LanguageClient/ServerUtils.js | 427 +++++ src/languageTools/LanguageClient/Utils.js | 88 + src/languageTools/LanguageClient/package.json | 19 + src/languageTools/LanguageClientWrapper.js | 627 +++++++ src/languageTools/LanguageTools.js | 119 ++ src/languageTools/PathConverters.js | 82 + src/languageTools/ToolingInfo.json | 41 + .../node/RegisterLanguageClientInfo.js | 290 +++ .../styles/default_provider_style.css | 134 ++ src/nls/root/strings.js | 5 +- tasks/npm-install.js | 35 +- test/SpecRunner.js | 12 + test/UnitTestSuite.js | 1 + .../clients/CommunicationTestClient/client.js | 121 ++ .../clients/CommunicationTestClient/main.js | 68 + .../CommunicationTestClient/package.json | 5 + .../clients/FeatureClient/client.js | 73 + .../clients/FeatureClient/main.js | 74 + .../clients/InterfaceTestClient/client.js | 130 ++ .../clients/InterfaceTestClient/main.js | 59 + .../clients/LoadSimpleClient/client.js | 37 + .../clients/LoadSimpleClient/main.js | 51 + .../clients/ModuleTestClient/client.js | 73 + .../clients/ModuleTestClient/main.js | 74 + .../clients/OptionsTestClient/client.js | 145 ++ .../clients/OptionsTestClient/main.js | 68 + .../project/sample1.txt | 1 + .../project/sample2.txt | 1 + .../server/lsp-test-server/main.js | 255 +++ .../server/lsp-test-server/package.json | 5 + test/spec/LanguageTools-test.js | 1599 +++++++++++++++++ 55 files changed, 7527 insertions(+), 647 deletions(-) delete mode 100644 src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js delete mode 100644 src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html create mode 100644 src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js delete mode 100644 src/extensions/default/JavaScriptCodeHints/keyboard.json create mode 100644 src/features/JumpToDefManager.js create mode 100644 src/features/ParameterHintsManager.js create mode 100644 src/features/PriorityBasedRegistration.js create mode 100644 src/htmlContent/parameter-hint-template.html create mode 100644 src/languageTools/BracketsToNodeInterface.js create mode 100644 src/languageTools/ClientLoader.js create mode 100644 src/languageTools/DefaultEventHandlers.js create mode 100644 src/languageTools/DefaultProviders.js create mode 100644 src/languageTools/LanguageClient/Connection.js create mode 100644 src/languageTools/LanguageClient/LanguageClient.js create mode 100644 src/languageTools/LanguageClient/NodeToBracketsInterface.js create mode 100644 src/languageTools/LanguageClient/ProtocolAdapter.js create mode 100644 src/languageTools/LanguageClient/ServerUtils.js create mode 100644 src/languageTools/LanguageClient/Utils.js create mode 100644 src/languageTools/LanguageClient/package.json create mode 100644 src/languageTools/LanguageClientWrapper.js create mode 100644 src/languageTools/LanguageTools.js create mode 100644 src/languageTools/PathConverters.js create mode 100644 src/languageTools/ToolingInfo.json create mode 100644 src/languageTools/node/RegisterLanguageClientInfo.js create mode 100644 src/languageTools/styles/default_provider_style.css create mode 100644 test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json create mode 100644 test/spec/LanguageTools-test-files/clients/FeatureClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/FeatureClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js create mode 100644 test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js create mode 100644 test/spec/LanguageTools-test-files/project/sample1.txt create mode 100644 test/spec/LanguageTools-test-files/project/sample2.txt create mode 100644 test/spec/LanguageTools-test-files/server/lsp-test-server/main.js create mode 100644 test/spec/LanguageTools-test-files/server/lsp-test-server/package.json create mode 100644 test/spec/LanguageTools-test.js diff --git a/.eslintrc.js b/.eslintrc.js index 40331166ab1..4ff0306efcb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -80,5 +80,15 @@ module.exports = { "Uint32Array": false, "WebSocket": false, "XMLHttpRequest": false + }, + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "script", + "ecmaFeatures": { + "arrowFunctions": true, + "binaryLiterals": true, + "blockBindings": true, + "classes": true + } } }; diff --git a/Gruntfile.js b/Gruntfile.js index fe7e2d0a1d0..0a276991b8c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -82,6 +82,9 @@ module.exports = function (grunt) { src: [ 'extensibility/node/**', 'JSUtils/node/**', + 'languageTools/node/**', + 'languageTools/styles/**', + 'languageTools/LanguageClient/**', '!extensibility/node/spec/**', '!extensibility/node/node_modules/**/{test,tst}/**/*', '!extensibility/node/node_modules/**/examples/**/*', diff --git a/src/brackets.js b/src/brackets.js index 9a21072cefd..4365249ef05 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -137,6 +137,10 @@ define(function (require, exports, module) { return PathUtils; } }); + + //load language features + require("features/ParameterHintsManager"); + require("features/JumpToDefManager"); // Load modules that self-register and just need to get included in the main project require("command/DefaultMenus"); @@ -155,6 +159,15 @@ define(function (require, exports, module) { require("JSUtils/Session"); require("JSUtils/ScopeManager"); + //load Language Tools Module + require("languageTools/PathConverters"); + require("languageTools/LanguageTools"); + require("languageTools/ClientLoader"); + require("languageTools/BracketsToNodeInterface"); + require("languageTools/DefaultProviders"); + require("languageTools/DefaultEventHandlers"); + + PerfUtils.addMeasurement("brackets module dependencies resolved"); // Local variables diff --git a/src/command/Commands.js b/src/command/Commands.js index e607880457e..dc147eeac6f 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -123,7 +123,7 @@ define(function (require, exports, module) { exports.NAVIGATE_SHOW_IN_FILE_TREE = "navigate.showInFileTree"; // DocumentCommandHandlers.js handleShowInTree() exports.NAVIGATE_SHOW_IN_OS = "navigate.showInOS"; // DocumentCommandHandlers.js handleShowInOS() exports.NAVIGATE_QUICK_OPEN = "navigate.quickOpen"; // QuickOpen.js doFileSearch() - exports.NAVIGATE_JUMPTO_DEFINITION = "navigate.jumptoDefinition"; // EditorManager.js _doJumpToDef() + exports.NAVIGATE_JUMPTO_DEFINITION = "navigate.jumptoDefinition"; // JumpToDefManager.js _doJumpToDef() exports.NAVIGATE_GOTO_DEFINITION = "navigate.gotoDefinition"; // QuickOpen.js doDefinitionSearch() exports.NAVIGATE_GOTO_LINE = "navigate.gotoLine"; // QuickOpen.js doGotoLine() exports.NAVIGATE_GOTO_FIRST_PROBLEM = "navigate.gotoFirstProblem"; // CodeInspection.js handleGotoFirstProblem() diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index 79897d47118..e9df922d40b 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -92,15 +92,6 @@ define(function (require, exports, module) { */ var _inlineDocsProviders = []; - /** - * Registered jump-to-definition providers. - * @see {@link #registerJumpToDefProvider}. - * @private - * @type {Array.} - */ - var _jumpToDefProviders = []; - - /** * DOM element to house any hidden editors created soley for inline widgets * @private @@ -423,19 +414,6 @@ define(function (require, exports, module) { _insertProviderSorted(_inlineDocsProviders, provider, priority); } - /** - * Registers a new jump-to-definition provider. When jump-to-definition is invoked each - * registered provider is asked if it wants to provide jump-to-definition results, given - * the current editor and cursor location. - * - * @param {function(!Editor, !{line:number, ch:number}):?$.Promise} provider - * The provider returns a promise that is resolved whenever it's done handling the operation, - * or returns null to indicate the provider doesn't want to respond to this case. It is entirely - * up to the provider to open the file containing the definition, select the appropriate text, etc. - */ - function registerJumpToDefProvider(provider) { - _jumpToDefProviders.push(provider); - } /** * @private @@ -705,55 +683,6 @@ define(function (require, exports, module) { return _lastFocusedEditor; } - - /** - * Asynchronously asks providers to handle jump-to-definition. - * @return {!Promise} Resolved when the provider signals that it's done; rejected if no - * provider responded or the provider that responded failed. - */ - function _doJumpToDef() { - var providers = _jumpToDefProviders; - var promise, - i, - result = new $.Deferred(); - - var editor = getActiveEditor(); - - if (editor) { - var pos = editor.getCursorPos(); - - PerfUtils.markStart(PerfUtils.JUMP_TO_DEFINITION); - - // Run through providers until one responds - for (i = 0; i < providers.length && !promise; i++) { - var provider = providers[i]; - promise = provider(editor, pos); - } - - // Will one of them will provide a result? - if (promise) { - promise.done(function () { - PerfUtils.addMeasurement(PerfUtils.JUMP_TO_DEFINITION); - result.resolve(); - }).fail(function () { - // terminate timer that was started above - PerfUtils.finalizeMeasurement(PerfUtils.JUMP_TO_DEFINITION); - result.reject(); - }); - } else { - // terminate timer that was started above - PerfUtils.finalizeMeasurement(PerfUtils.JUMP_TO_DEFINITION); - result.reject(); - } - - } else { - result.reject(); - } - - return result.promise(); - } - - /** * file removed from pane handler. * @param {jQuery.Event} e @@ -797,10 +726,6 @@ define(function (require, exports, module) { CommandManager.register(Strings.CMD_TOGGLE_QUICK_DOCS, Commands.TOGGLE_QUICK_DOCS, function () { return _toggleInlineWidget(_inlineDocsProviders, Strings.ERROR_QUICK_DOCS_PROVIDER_NOT_FOUND); }); - CommandManager.register(Strings.CMD_JUMPTO_DEFINITION, Commands.NAVIGATE_JUMPTO_DEFINITION, _doJumpToDef); - - // Create PerfUtils measurement - PerfUtils.createPerfMeasurement("JUMP_TO_DEFINITION", "Jump-To-Definiiton"); MainViewManager.on("currentFileChange", _handleCurrentFileChange); MainViewManager.on("workingSetRemove workingSetRemoveList", _handleRemoveFromPaneView); @@ -830,7 +755,6 @@ define(function (require, exports, module) { exports.registerInlineEditProvider = registerInlineEditProvider; exports.registerInlineDocsProvider = registerInlineDocsProvider; - exports.registerJumpToDefProvider = registerJumpToDefProvider; // Deprecated exports.registerCustomViewer = registerCustomViewer; diff --git a/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js b/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js deleted file mode 100644 index ce84b9cea7d..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/ParameterHintManager.js +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Copyright (c) 2013 - present 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. - * - */ - -define(function (require, exports, module) { - "use strict"; - - var _ = brackets.getModule("thirdparty/lodash"); - - var Commands = brackets.getModule("command/Commands"), - CommandManager = brackets.getModule("command/CommandManager"), - KeyEvent = brackets.getModule("utils/KeyEvent"), - Menus = brackets.getModule("command/Menus"), - Strings = brackets.getModule("strings"), - HintsUtils2 = require("HintUtils2"), - ScopeManager = brackets.getModule("JSUtils/ScopeManager"); - - - /** @const {string} Show Function Hint command ID */ - var SHOW_PARAMETER_HINT_CMD_ID = "showParameterHint", // string must MATCH string in native code (brackets_extensions) - PUSH_EXISTING_HINT = true, - OVERWRITE_EXISTING_HINT = false, - hintContainerHTML = require("text!ParameterHintTemplate.html"), - KeyboardPrefs = JSON.parse(require("text!keyboard.json")); - - var $hintContainer, // function hint container - $hintContent, // function hint content holder - - /** @type {{inFunctionCall: boolean, functionCallPos: {line: number, ch: number}, - * fnType: Array.") - .append(_.escape(param)) - .addClass("current-parameter")); - } else { - $hintContent.append(_.escape(param)); - } - } - - if (hints.parameters.length > 0) { - HintsUtils2.formatParameterHint(hints.parameters, appendSeparators, appendParameter); - } else { - $hintContent.append(_.escape(Strings.NO_ARGUMENTS)); - } - } - - /** - * Save the state of the current hint. Called when popping up a parameter hint - * for a parameter, when the parameter already part of an existing parameter - * hint. - */ - function pushHintOnStack() { - hintStack.push(hintState); - } - - /** - * Restore the state of the previous function hint. - * - * @return {boolean} - true the a parameter hint has been popped, false otherwise. - */ - function popHintFromStack() { - if (hintStack.length > 0) { - hintState = hintStack.pop(); - hintState.visible = false; - return true; - } - - return false; - } - - /** - * Reset the function hint stack. - */ - function clearFunctionHintStack() { - hintStack = []; - } - - /** - * Test if the function call at the cursor is different from the currently displayed - * function hint. - * - * @param {{line:number, ch:number}} functionCallPos - the offset of the function call. - * @return {boolean} - */ - function hasFunctionCallPosChanged(functionCallPos) { - var oldFunctionCallPos = hintState.functionCallPos; - return (oldFunctionCallPos === undefined || - oldFunctionCallPos.line !== functionCallPos.line || - oldFunctionCallPos.ch !== functionCallPos.ch); - } - - /** - * Dismiss the function hint. - * - */ - function dismissHint() { - - if (hintState.visible) { - $hintContainer.hide(); - $hintContent.empty(); - hintState = {}; - session.editor.off("cursorActivity", handleCursorActivity); - - if (!preserveHintStack) { - clearFunctionHintStack(); - } - } - } - - /** - * Pop up a function hint on the line above the caret position. - * - * @param {boolean=} pushExistingHint - if true, push the existing hint on the stack. Default is false, not - * to push the hint. - * @param {string=} hint - function hint string from tern. - * @param {{inFunctionCall: boolean, functionCallPos: - * {line: number, ch: number}}=} functionInfo - - * if the functionInfo is already known, it can be passed in to avoid - * figuring it out again. - * @return {jQuery.Promise} - The promise will not complete until the - * hint has completed. Returns null, if the function hint is already - * displayed or there is no function hint at the cursor. - * - */ - function popUpHint(pushExistingHint, hint, functionInfo) { - - functionInfo = functionInfo || session.getFunctionInfo(); - if (!functionInfo.inFunctionCall) { - dismissHint(); - return null; - } - - if (hasFunctionCallPosChanged(functionInfo.functionCallPos)) { - - var pushHint = pushExistingHint && isHintDisplayed(); - if (pushHint) { - pushHintOnStack(); - preserveHintStack = true; - } - - dismissHint(); - preserveHintStack = false; - } else if (isHintDisplayed()) { - return null; - } - - hintState.functionCallPos = functionInfo.functionCallPos; - - var request = null; - var $deferredPopUp = $.Deferred(); - - if (!hint) { - request = ScopeManager.requestParameterHint(session, functionInfo.functionCallPos); - } else { - session.setFnType(hint); - request = $.Deferred(); - request.resolveWith(null, [hint]); - $deferredPopUp.resolveWith(null); - } - - request.done(function (fnType) { - var cm = session.editor._codeMirror, - pos = cm.charCoords(functionInfo.functionCallPos); - - formatHint(functionInfo); - - $hintContainer.show(); - positionHint(pos.left, pos.top, pos.bottom); - hintState.visible = true; - hintState.fnType = fnType; - - session.editor.on("cursorActivity", handleCursorActivity); - $deferredPopUp.resolveWith(null); - }).fail(function () { - hintState = {}; - }); - - return $deferredPopUp; - } - - /** - * Pop up a function hint on the line above the caret position if the character before - * the current cursor is an open parenthesis - * - * @return {jQuery.Promise} - The promise will not complete until the - * hint has completed. Returns null, if the function hint is already - * displayed or there is no function hint at the cursor. - */ - function popUpHintAtOpenParen() { - var functionInfo = session.getFunctionInfo(); - if (functionInfo.inFunctionCall) { - var token = session.getToken(); - - if (token && token.string === "(") { - return popUpHint(); - } - } else { - dismissHint(); - } - - return null; - } - - /** - * Show the parameter the cursor is on in bold when the cursor moves. - * Dismiss the pop up when the cursor moves off the function. - */ - handleCursorActivity = function () { - var functionInfo = session.getFunctionInfo(); - - if (functionInfo.inFunctionCall) { - // If in a different function hint, then dismiss the old one and - // display the new one if there is one on the stack - if (hasFunctionCallPosChanged(functionInfo.functionCallPos)) { - if (popHintFromStack()) { - var poppedFunctionCallPos = hintState.functionCallPos, - currentFunctionCallPos = functionInfo.functionCallPos; - - if (poppedFunctionCallPos.line === currentFunctionCallPos.line && - poppedFunctionCallPos.ch === currentFunctionCallPos.ch) { - preserveHintStack = true; - popUpHint(OVERWRITE_EXISTING_HINT, - hintState.fnType, functionInfo); - preserveHintStack = false; - return; - } - } else { - dismissHint(); - } - } - - formatHint(functionInfo); - return; - } - - dismissHint(); - }; - - /** - * Enable cursor tracking in the current session. - * - * @param {Session} session - session to start cursor tracking on. - */ - function startCursorTracking(session) { - session.editor.on("cursorActivity", handleCursorActivity); - } - - /** - * Stop cursor tracking in the current session. - * - * Use this to move the cursor without changing the function hint state. - * - * @param {Session} session - session to stop cursor tracking on. - */ - function stopCursorTracking(session) { - session.editor.off("cursorActivity", handleCursorActivity); - } - - /** - * Show a parameter hint in its own pop-up. - * - */ - function handleShowParameterHint() { - - // Pop up function hint - popUpHint(); - } - - /** - * Install function hint listeners. - * - * @param {Editor} editor - editor context on which to listen for - * changes - */ - function installListeners(editor) { - editor.on("keydown.ParameterHints", function (event, editor, domEvent) { - if (domEvent.keyCode === KeyEvent.DOM_VK_ESCAPE) { - dismissHint(); - } - }).on("scroll.ParameterHints", function () { - dismissHint(); - }); - } - - /** - * Clean up after installListeners() - * @param {!Editor} editor - */ - function uninstallListeners(editor) { - editor.off(".ParameterHints"); - } - - /** - * Add the function hint command at start up. - */ - function addCommands() { - /* Register the command handler */ - CommandManager.register(Strings.CMD_SHOW_PARAMETER_HINT, SHOW_PARAMETER_HINT_CMD_ID, handleShowParameterHint); - - // Add the menu items - var menu = Menus.getMenu(Menus.AppMenuBar.EDIT_MENU); - if (menu) { - menu.addMenuItem(SHOW_PARAMETER_HINT_CMD_ID, KeyboardPrefs.showParameterHint, Menus.AFTER, Commands.SHOW_CODE_HINTS); - } - - // Close the function hint when commands are executed, except for the commands - // to show function hints for code hints. - CommandManager.on("beforeExecuteCommand", function (event, commandId) { - if (commandId !== SHOW_PARAMETER_HINT_CMD_ID && - commandId !== Commands.SHOW_CODE_HINTS) { - dismissHint(); - } - }); - } - - // Create the function hint container - $hintContainer = $(hintContainerHTML).appendTo($("body")); - $hintContent = $hintContainer.find(".function-hint-content"); - - exports.PUSH_EXISTING_HINT = PUSH_EXISTING_HINT; - exports.addCommands = addCommands; - exports.dismissHint = dismissHint; - exports.installListeners = installListeners; - exports.uninstallListeners = uninstallListeners; - exports.isHintDisplayed = isHintDisplayed; - exports.popUpHint = popUpHint; - exports.popUpHintAtOpenParen = popUpHintAtOpenParen; - exports.setSession = setSession; - exports.startCursorTracking = startCursorTracking; - exports.stopCursorTracking = stopCursorTracking; - -}); diff --git a/src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html b/src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html deleted file mode 100644 index 04f8a9c04a2..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/ParameterHintTemplate.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
diff --git a/src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js b/src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js new file mode 100644 index 00000000000..72c9d27ac73 --- /dev/null +++ b/src/extensions/default/JavaScriptCodeHints/ParameterHintsProvider.js @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2013 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var ScopeManager = brackets.getModule("JSUtils/ScopeManager"), + OVERWRITE_EXISTING_HINT = false; + + function JSParameterHintsProvider() { + this.hintState = {}; + this.hintStack = []; + this.preserveHintStack = null; // close a function hint without clearing stack + this.session = null; // current editor session, updated by main + } + + /** + * Update the current session for use by the Function Hint Manager. + * + * @param {Session} value - current session. + */ + JSParameterHintsProvider.prototype.setSession = function (value) { + this.session = value; + }; + + /** + * Test if a function hint is being displayed. + * + * @return {boolean} - true if a function hint is being displayed, false + * otherwise. + */ + JSParameterHintsProvider.prototype.isHintDisplayed = function () { + return this.hintState.visible === true; + }; + + /** + * Save the state of the current hint. Called when popping up a parameter hint + * for a parameter, when the parameter already part of an existing parameter + * hint. + */ + JSParameterHintsProvider.prototype.pushHintOnStack = function () { + this.hintStack.push(this.hintState); + }; + + /** + * Restore the state of the previous function hint. + * + * @return {boolean} - true the a parameter hint has been popped, false otherwise. + */ + JSParameterHintsProvider.prototype.popHintFromStack = function () { + if (this.hintStack.length > 0) { + this.hintState = this.hintStack.pop(); + this.hintState.visible = false; + return true; + } + + return false; + }; + + /** + * Reset the function hint stack. + */ + JSParameterHintsProvider.prototype.clearFunctionHintStack = function () { + this.hintStack = []; + }; + + /** + * Test if the function call at the cursor is different from the currently displayed + * function hint. + * + * @param {{line:number, ch:number}} functionCallPos - the offset of the function call. + * @return {boolean} + */ + JSParameterHintsProvider.prototype.hasFunctionCallPosChanged = function (functionCallPos) { + var oldFunctionCallPos = this.hintState.functionCallPos; + return (oldFunctionCallPos === undefined || + oldFunctionCallPos.line !== functionCallPos.line || + oldFunctionCallPos.ch !== functionCallPos.ch); + }; + + /** + * Dismiss the function hint. + * + */ + JSParameterHintsProvider.prototype.cleanHintState = function () { + if (this.hintState.visible) { + if (!this.preserveHintStack) { + this.clearFunctionHintStack(); + } + } + }; + + /** + * Pop up a function hint on the line above the caret position. + * + * @param {boolean=} pushExistingHint - if true, push the existing hint on the stack. Default is false, not + * to push the hint. + * @param {string=} hint - function hint string from tern. + * @param {{inFunctionCall: boolean, functionCallPos: + * {line: number, ch: number}}=} functionInfo - + * if the functionInfo is already known, it can be passed in to avoid + * figuring it out again. + * @return {jQuery.Promise} - The promise will not complete until the + * hint has completed. Returns null, if the function hint is already + * displayed or there is no function hint at the cursor. + * + */ + JSParameterHintsProvider.prototype._getParameterHint = function (pushExistingHint, hint, functionInfo) { + var result = $.Deferred(); + functionInfo = functionInfo || this.session.getFunctionInfo(); + if (!functionInfo.inFunctionCall) { + this.cleanHintState(); + return result.reject(null); + } + + if (this.hasFunctionCallPosChanged(functionInfo.functionCallPos)) { + + var pushHint = pushExistingHint && this.isHintDisplayed(); + if (pushHint) { + this.pushHintOnStack(); + this.preserveHintStack = true; + } + + this.cleanHintState(); + this.preserveHintStack = false; + } else if (this.isHintDisplayed()) { + return result.reject(null); + } + + this.hintState.functionCallPos = functionInfo.functionCallPos; + + var request = null; + if (!hint) { + request = ScopeManager.requestParameterHint(this.session, functionInfo.functionCallPos); + } else { + this.session.setFnType(hint); + request = $.Deferred(); + request.resolveWith(null, [hint]); + } + + var self = this; + request.done(function (fnType) { + var hints = self.session.getParameterHint(functionInfo.functionCallPos); + hints.functionCallPos = functionInfo.functionCallPos; + result.resolve(hints); + }).fail(function () { + self.hintState = {}; + result.reject(null); + }); + + return result; + }; + + JSParameterHintsProvider.prototype.hasParameterHints = function () { + var functionInfo = this.session.getFunctionInfo(); + + return functionInfo.inFunctionCall; + }; + + JSParameterHintsProvider.prototype.getParameterHints = function (explicit, onCursorActivity) { + var functionInfo = this.session.getFunctionInfo(), + result = null; + + if (!onCursorActivity) { + if (functionInfo.inFunctionCall) { + var token = this.session.getToken(); + + if ((token && token.string === "(") || explicit) { + return this._getParameterHint(); + } + } else { + this.cleanHintState(); + } + + return $.Deferred().reject(null); + } + + if (!functionInfo.inFunctionCall) { + this.cleanHintState(); + return $.Deferred().reject(null); + } + + // If in a different function hint, then dismiss the old one and + // display the new one if there is one on the stack + if (this.hasFunctionCallPosChanged(functionInfo.functionCallPos)) { + if (this.popHintFromStack()) { + var poppedFunctionCallPos = this.hintState.functionCallPos, + currentFunctionCallPos = this.functionInfo.functionCallPos; + + if (poppedFunctionCallPos.line === currentFunctionCallPos.line && + poppedFunctionCallPos.ch === currentFunctionCallPos.ch) { + this.preserveHintStack = true; + result = this._getParameterHint(OVERWRITE_EXISTING_HINT, + this.hintState.fnType, functionInfo); + this.preserveHintStack = false; + return result; + } + } else { + this.cleanHintState(); + } + } + + var hints = this.session.getParameterHint(functionInfo.functionCallPos); + hints.functionCallPos = functionInfo.functionCallPos; + return $.Deferred().resolve(hints); + }; + + exports.JSParameterHintsProvider = JSParameterHintsProvider; +}); diff --git a/src/extensions/default/JavaScriptCodeHints/keyboard.json b/src/extensions/default/JavaScriptCodeHints/keyboard.json deleted file mode 100644 index d4d4e5d1345..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/keyboard.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "showParameterHint": [ - { - "key": "Ctrl-Shift-Space" - }, - { - "key": "Ctrl-Shift-Space", - "platform": "mac" - } - ] -} \ No newline at end of file diff --git a/src/extensions/default/JavaScriptCodeHints/main.js b/src/extensions/default/JavaScriptCodeHints/main.js index 66b42a022f3..586ec1004c7 100644 --- a/src/extensions/default/JavaScriptCodeHints/main.js +++ b/src/extensions/default/JavaScriptCodeHints/main.js @@ -26,22 +26,24 @@ define(function (require, exports, module) { var _ = brackets.getModule("thirdparty/lodash"); - var CodeHintManager = brackets.getModule("editor/CodeHintManager"), - EditorManager = brackets.getModule("editor/EditorManager"), - Commands = brackets.getModule("command/Commands"), - CommandManager = brackets.getModule("command/CommandManager"), - LanguageManager = brackets.getModule("language/LanguageManager"), - AppInit = brackets.getModule("utils/AppInit"), - ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), - StringMatch = brackets.getModule("utils/StringMatch"), - ProjectManager = brackets.getModule("project/ProjectManager"), - PreferencesManager = brackets.getModule("preferences/PreferencesManager"), - Strings = brackets.getModule("strings"), - ParameterHintManager = require("ParameterHintManager"), - HintUtils = brackets.getModule("JSUtils/HintUtils"), - ScopeManager = brackets.getModule("JSUtils/ScopeManager"), - Session = brackets.getModule("JSUtils/Session"), - Acorn = require("node_modules/acorn/dist/acorn"); + var CodeHintManager = brackets.getModule("editor/CodeHintManager"), + EditorManager = brackets.getModule("editor/EditorManager"), + Commands = brackets.getModule("command/Commands"), + CommandManager = brackets.getModule("command/CommandManager"), + LanguageManager = brackets.getModule("language/LanguageManager"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + StringMatch = brackets.getModule("utils/StringMatch"), + ProjectManager = brackets.getModule("project/ProjectManager"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + Strings = brackets.getModule("strings"), + JSParameterHintsProvider = require("./ParameterHintsProvider").JSParameterHintsProvider, + ParameterHintsManager = brackets.getModule("features/ParameterHintsManager"), + HintUtils = brackets.getModule("JSUtils/HintUtils"), + ScopeManager = brackets.getModule("JSUtils/ScopeManager"), + Session = brackets.getModule("JSUtils/Session"), + JumpToDefManager = brackets.getModule("features/JumpToDefManager"), + Acorn = require("node_modules/acorn/dist/acorn"); var session = null, // object that encapsulates the current session state cachedCursor = null, // last cursor of the current hinting session @@ -55,7 +57,8 @@ define(function (require, exports, module) { ignoreChange; // can ignore next "change" event if true; // Languages that support inline JavaScript - var _inlineScriptLanguages = ["html", "php"]; + var _inlineScriptLanguages = ["html", "php"], + phProvider = new JSParameterHintsProvider(); // Define the detectedExclusions which are files that have been detected to cause Tern to run out of control. PreferencesManager.definePreference("jscodehints.detectedExclusions", "array", [], { @@ -642,7 +645,7 @@ define(function (require, exports, module) { session = new Session(editor); ScopeManager.handleEditorChange(session, editor.document, previousEditor ? previousEditor.document : null); - ParameterHintManager.setSession(session); + phProvider.setSession(session); cachedHints = null; } @@ -667,11 +670,9 @@ define(function (require, exports, module) { .on(HintUtils.eventName("change"), function (event, editor, changeList) { if (!ignoreChange) { ScopeManager.handleFileChange(changeList); - ParameterHintManager.popUpHintAtOpenParen(); } ignoreChange = false; }); - ParameterHintManager.installListeners(editor); } else { session = null; } @@ -686,7 +687,6 @@ define(function (require, exports, module) { function uninstallEditorListeners(editor) { if (editor) { editor.off(HintUtils.eventName("change")); - ParameterHintManager.uninstallListeners(editor); } } @@ -719,10 +719,21 @@ define(function (require, exports, module) { installEditorListeners(current, previous); } - /* - * Handle JumptoDefiniton menu/keyboard command. + function setJumpPosition(curPos) { + EditorManager.getCurrentFullEditor().setCursorPos(curPos.line, curPos.ch, true); + } + + function JSJumpToDefProvider() { + } + + JSJumpToDefProvider.prototype.canJumpToDef = function (editor, implicitChar) { + return true; + }; + + /** + * Method to handle jump to definition feature. */ - function handleJumpToDefinition() { + JSJumpToDefProvider.prototype.doJumpToDef = function () { var offset, handleJumpResponse; @@ -856,7 +867,7 @@ define(function (require, exports, module) { requestJumpToDef(session, offset); return result.promise(); - } + }; /* * Helper for QuickEdit jump-to-definition request. @@ -891,18 +902,18 @@ define(function (require, exports, module) { // immediately install the current editor installEditorListeners(EditorManager.getActiveEditor()); + ParameterHintsManager.registerHintProvider(phProvider, ["javascript"], 0); // init - EditorManager.registerJumpToDefProvider(handleJumpToDefinition); + var jdProvider = new JSJumpToDefProvider(); + JumpToDefManager.registerJumpToDefProvider(jdProvider, ["javascript"], 0); var jsHints = new JSHints(); CodeHintManager.registerHintProvider(jsHints, HintUtils.SUPPORTED_LANGUAGES, 0); - ParameterHintManager.addCommands(); - // for unit testing exports.getSession = getSession; exports.jsHintProvider = jsHints; exports.initializeSession = initializeSession; - exports.handleJumpToDefinition = handleJumpToDefinition; + exports.handleJumpToDefinition = jdProvider.doJumpToDef.bind(jdProvider); }); }); diff --git a/src/extensions/default/JavaScriptCodeHints/unittests.js b/src/extensions/default/JavaScriptCodeHints/unittests.js index e8634d07219..7e31ebb28d2 100644 --- a/src/extensions/default/JavaScriptCodeHints/unittests.js +++ b/src/extensions/default/JavaScriptCodeHints/unittests.js @@ -22,7 +22,7 @@ */ /*jslint regexp: true */ -/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, beforeFirst, afterLast */ +/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, waitsForFail, beforeFirst, afterLast */ define(function (require, exports, module) { "use strict"; @@ -41,7 +41,8 @@ define(function (require, exports, module) { ScopeManager = brackets.getModule("JSUtils/ScopeManager"), HintUtils = brackets.getModule("JSUtils/HintUtils"), HintUtils2 = require("HintUtils2"), - ParameterHintManager = require("ParameterHintManager"); + ParameterHintProvider = require("ParameterHintsProvider").JSParameterHintsProvider, + phProvider = new ParameterHintProvider(); var extensionPath = FileUtils.getNativeModuleDirectoryPath(module), testPath = extensionPath + "/unittest-files/basic-test-files/file1.js", @@ -341,39 +342,26 @@ define(function (require, exports, module) { * Verify there is no parameter hint at the current cursor. */ function expectNoParameterHint() { - expect(ParameterHintManager.popUpHint()).toBe(null); + var requestStatus = undefined; + runs(function () { + var request = phProvider._getParameterHint(); + request.fail(function (status) { + requestStatus = status; + }); + + waitsForFail(request, "ParameterHints"); + }); + + runs(function () { + expect(requestStatus).toBe(null); + }); } /** * Verify the parameter hint is not visible. */ function expectParameterHintClosed() { - expect(ParameterHintManager.isHintDisplayed()).toBe(false); - } - - /* - * Wait for a hint response object to resolve, then apply a callback - * to the result - * - * @param {Object + jQuery.Deferred} hintObj - a hint response object, - * possibly deferred - * @param {Function} callback - the callback to apply to the resolved - * hint response object - */ - function _waitForParameterHint(hintObj, callback) { - var complete = false, - hint = null; - - hintObj.done(function () { - hint = JSCodeHints.getSession().getParameterHint(); - complete = true; - }); - - waitsFor(function () { - return complete; - }, "Expected parameter hint did not resolve", 3000); - - runs(function () { callback(hint); }); + expect(phProvider.isHintDisplayed()).toBe(false); } /** @@ -386,12 +374,9 @@ define(function (require, exports, module) { * @param {number} expectedParameter - the parameter at cursor. */ function expectParameterHint(expectedParams, expectedParameter) { - var request = ParameterHintManager.popUpHint(); - if (expectedParams === null) { - expect(request).toBe(null); - return; - } - + var requestHints = undefined, + request = null; + function expectHint(hint) { var params = hint.parameters, n = params.length, @@ -413,11 +398,29 @@ define(function (require, exports, module) { } } + + runs(function () { + request = phProvider._getParameterHint(); + + if (expectedParams === null) { + request.fail(function (result) { + requestHints = result; + }); + + waitsForFail(request, "ParameterHints"); + } else { + request.done(function (result) { + requestHints = result; + }); + + waitsForDone(request, "ParameterHints"); + } + }); - if (request) { - _waitForParameterHint(request, expectHint); + if (expectedParams === null) { + expect(requestHints).toBe(null); } else { - expectHint(JSCodeHints.getSession().getParameterHint()); + expectHint(requestHints); } } diff --git a/src/extensions/default/JavaScriptQuickEdit/unittests.js b/src/extensions/default/JavaScriptQuickEdit/unittests.js index 5302abca973..a452d5b0fb2 100644 --- a/src/extensions/default/JavaScriptQuickEdit/unittests.js +++ b/src/extensions/default/JavaScriptQuickEdit/unittests.js @@ -21,7 +21,7 @@ * */ -/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone */ +/*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, waitsForFail */ define(function (require, exports, module) { "use strict"; @@ -275,7 +275,7 @@ define(function (require, exports, module) { describe("Code hints tests within quick edit window ", function () { var JSCodeHints, - ParameterHintManager; + ParameterHintProvider; /* * Ask provider for hints at current cursor position; expect it to @@ -345,31 +345,6 @@ define(function (require, exports, module) { }); } - /* - * Wait for a hint response object to resolve, then apply a callback - * to the result - * - * @param {Object + jQuery.Deferred} hintObj - a hint response object, - * possibly deferred - * @param {Function} callback - the callback to apply to the resolved - * hint response object - */ - function _waitForParameterHint(hintObj, callback) { - var complete = false, - hint = null; - - hintObj.done(function () { - hint = JSCodeHints.getSession().getParameterHint(); - complete = true; - }); - - waitsFor(function () { - return complete; - }, "Expected parameter hint did not resolve", 3000); - - runs(function () { callback(hint); }); - } - /** * Show a function hint based on the code at the cursor. Verify the * hint matches the passed in value. @@ -380,11 +355,8 @@ define(function (require, exports, module) { * @param {number} expectedParameter - the parameter at cursor. */ function expectParameterHint(expectedParams, expectedParameter) { - var request = ParameterHintManager.popUpHint(); - if (expectedParams === null) { - expect(request).toBe(null); - return; - } + var requestHints = undefined, + request = null; function expectHint(hint) { var params = hint.parameters, @@ -408,10 +380,28 @@ define(function (require, exports, module) { } - if (request) { - _waitForParameterHint(request, expectHint); + runs(function () { + request = ParameterHintProvider._getParameterHint(); + + if (expectedParams === null) { + request.fail(function (result) { + requestHints = result; + }); + + waitsForFail(request, "ParameterHints"); + } else { + request.done(function (result) { + requestHints = result; + }); + + waitsForDone(request, "ParameterHints"); + } + }); + + if (expectedParams === null) { + expect(requestHints).toBe(null); } else { - expectHint(JSCodeHints.getSession().getParameterHint()); + expectHint(requestHints); } } @@ -462,7 +452,7 @@ define(function (require, exports, module) { var extensionRequire = testWindow.brackets.getModule("utils/ExtensionLoader"). getRequireContextForExtension("JavaScriptCodeHints"); JSCodeHints = extensionRequire("main"); - ParameterHintManager = extensionRequire("ParameterHintManager"); + ParameterHintProvider = extensionRequire("ParameterHintsProvider").JSParameterHintsProvider(); } beforeEach(function () { @@ -472,7 +462,7 @@ define(function (require, exports, module) { afterEach(function () { JSCodeHints = null; - ParameterHintManager = null; + ParameterHintProvider = null; }); it("should see code hint lists in quick editor", function () { diff --git a/src/features/JumpToDefManager.js b/src/features/JumpToDefManager.js new file mode 100644 index 00000000000..570c16fe3a2 --- /dev/null +++ b/src/features/JumpToDefManager.js @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var Commands = require("command/Commands"), + Strings = require("strings"), + AppInit = require("utils/AppInit"), + CommandManager = require("command/CommandManager"), + EditorManager = require("editor/EditorManager"), + ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler; + + var _providerRegistrationHandler = new ProviderRegistrationHandler(), + registerJumpToDefProvider = _providerRegistrationHandler.registerProvider.bind(_providerRegistrationHandler), + removeJumpToDefProvider = _providerRegistrationHandler.removeProvider.bind(_providerRegistrationHandler); + + + /** + * Asynchronously asks providers to handle jump-to-definition. + * @return {!Promise} Resolved when the provider signals that it's done; rejected if no + * provider responded or the provider that responded failed. + */ + function _doJumpToDef() { + var request = null, + result = new $.Deferred(), + jumpToDefProvider = null, + editor = EditorManager.getActiveEditor(); + + if (editor) { + // Find a suitable provider, if any + var language = editor.getLanguageForSelection(), + enabledProviders = _providerRegistrationHandler.getProvidersForLanguageId(language.getId()); + + + enabledProviders.some(function (item, index) { + if (item.provider.canJumpToDef(editor)) { + jumpToDefProvider = item.provider; + return true; + } + }); + + if (jumpToDefProvider) { + request = jumpToDefProvider.doJumpToDef(editor); + + if (request) { + request.done(function () { + result.resolve(); + }).fail(function () { + result.reject(); + }); + } else { + result.reject(); + } + } else { + result.reject(); + } + } else { + result.reject(); + } + + return result.promise(); + } + + CommandManager.register(Strings.CMD_JUMPTO_DEFINITION, Commands.NAVIGATE_JUMPTO_DEFINITION, _doJumpToDef); + + exports.registerJumpToDefProvider = registerJumpToDefProvider; + exports.removeJumpToDefProvider = removeJumpToDefProvider; +}); diff --git a/src/features/ParameterHintsManager.js b/src/features/ParameterHintsManager.js new file mode 100644 index 00000000000..adf2b5c5352 --- /dev/null +++ b/src/features/ParameterHintsManager.js @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var _ = require("thirdparty/lodash"); + + var Commands = require("command/Commands"), + AppInit = require("utils/AppInit"), + CommandManager = require("command/CommandManager"), + EditorManager = require("editor/EditorManager"), + Menus = require("command/Menus"), + KeyEvent = require("utils/KeyEvent"), + Strings = require("strings"), + ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler; + + + /** @const {string} Show Function Hint command ID */ + var SHOW_PARAMETER_HINT_CMD_ID = "showParameterHint", // string must MATCH string in native code (brackets_extensions) + hintContainerHTML = require("text!htmlContent/parameter-hint-template.html"), + KeyboardPrefs = { + "showParameterHint": [ + { + "key": "Ctrl-Shift-Space" + }, + { + "key": "Ctrl-Shift-Space", + "platform": "mac" + } + ] + }; + + var $hintContainer, // function hint container + $hintContent, // function hint content holder + hintState = {}, + lastChar = null, + sessionEditor = null, + keyDownEditor = null; + + // Constants + var POINTER_TOP_OFFSET = 4, // Size of margin + border of hint. + POSITION_BELOW_OFFSET = 4; // Amount to adjust to top position when the preview bubble is below the text + + // keep jslint from complaining about handleCursorActivity being used before + // it was defined. + var handleCursorActivity; + + var _providerRegistrationHandler = new ProviderRegistrationHandler(), + registerHintProvider = _providerRegistrationHandler.registerProvider.bind(_providerRegistrationHandler), + removeHintProvider = _providerRegistrationHandler.removeProvider.bind(_providerRegistrationHandler); + + /** + * Position a function hint. + * + * @param {number} xpos + * @param {number} ypos + * @param {number} ybot + */ + function positionHint(xpos, ypos, ybot) { + var hintWidth = $hintContainer.width(), + hintHeight = $hintContainer.height(), + top = ypos - hintHeight - POINTER_TOP_OFFSET, + left = xpos, + $editorHolder = $("#editor-holder"), + editorLeft; + + if ($editorHolder.offset() === undefined) { + // this happens in jasmine tests that run + // without a windowed document. + return; + } + + editorLeft = $editorHolder.offset().left; + left = Math.max(left, editorLeft); + left = Math.min(left, editorLeft + $editorHolder.width() - hintWidth); + + if (top < 0) { + $hintContainer.removeClass("preview-bubble-above"); + $hintContainer.addClass("preview-bubble-below"); + top = ybot + POSITION_BELOW_OFFSET; + $hintContainer.offset({ + left: left, + top: top + }); + } else { + $hintContainer.removeClass("preview-bubble-below"); + $hintContainer.addClass("preview-bubble-above"); + $hintContainer.offset({ + left: left, + top: top - POINTER_TOP_OFFSET + }); + } + } + + /** + * Format the given parameter array. Handles separators between + * parameters, syntax for optional parameters, and the order of the + * parameter type and parameter name. + * + * @param {!Array.<{name: string, type: string, isOptional: boolean}>} params - + * array of parameter descriptors + * @param {function(string)=} appendSeparators - callback function to append separators. + * The separator is passed to the callback. + * @param {function(string, number)=} appendParameter - callback function to append parameter. + * The formatted parameter type and name is passed to the callback along with the + * current index of the parameter. + * @param {boolean=} typesOnly - only show parameter types. The + * default behavior is to include both parameter names and types. + * @return {string} - formatted parameter hint + */ + function _formatParameterHint(params, appendSeparators, appendParameter, typesOnly) { + var result = "", + pendingOptional = false; + + appendParameter("(", "", -1); + params.forEach(function (value, i) { + var param = value.label || value.type, + documentation = value.documentation, + separators = ""; + + if (value.isOptional) { + // if an optional param is following by an optional parameter, then + // terminate the bracket. Otherwise enclose a required parameter + // in the same bracket. + if (pendingOptional) { + separators += "]"; + } + + pendingOptional = true; + } + + if (i > 0) { + separators += ", "; + } + + if (value.isOptional) { + separators += "["; + } + + if (appendSeparators) { + appendSeparators(separators); + } + + result += separators; + + if (!typesOnly && value.name) { + param += " " + value.name; + } + + if (appendParameter) { + appendParameter(param, documentation, i); + } + + result += param; + + }); + + if (pendingOptional) { + if (appendSeparators) { + appendSeparators("]"); + } + + result += "]"; + } + appendParameter(")", "", -1); + + return result; + } + + /** + * Bold the parameter at the caret. + * + * @param {{inFunctionCall: boolean, functionCallPos: {line: number, ch: number}}} functionInfo - + * tells if the caret is in a function call and the position + * of the function call. + */ + function formatHint(hints) { + $hintContent.empty(); + $hintContent.addClass("brackets-hints"); + + function appendSeparators(separators) { + $hintContent.append(separators); + } + + function appendParameter(param, documentation, index) { + if (hints.currentIndex === index) { + $hintContent.append($("") + .append(_.escape(param)) + .addClass("current-parameter")); + } else { + $hintContent.append($("") + .append(_.escape(param)) + .addClass("parameter")); + } + } + + if (hints.parameters.length > 0) { + _formatParameterHint(hints.parameters, appendSeparators, appendParameter); + } else { + $hintContent.append(_.escape(Strings.NO_ARGUMENTS)); + } + } + + /** + * Dismiss the function hint. + * + */ + function dismissHint(editor) { + if (hintState.visible) { + $hintContainer.hide(); + $hintContent.empty(); + hintState = {}; + + if (editor) { + editor.off("cursorActivity.ParameterHinting", handleCursorActivity); + sessionEditor = null; + } else if (sessionEditor) { + sessionEditor.off("cursorActivity.ParameterHinting", handleCursorActivity); + sessionEditor = null; + } + } + } + + /** + * Pop up a function hint on the line above the caret position. + * + * @param {object=} editor - current Active Editor + * @param {boolean} True if hints are invoked through cursor activity. + * @return {jQuery.Promise} - The promise will not complete until the + * hint has completed. Returns null, if the function hint is already + * displayed or there is no function hint at the cursor. + * + */ + function popUpHint(editor, explicit, onCursorActivity) { + var request = null; + var $deferredPopUp = $.Deferred(); + var sessionProvider = null; + + dismissHint(editor); + // Find a suitable provider, if any + var language = editor.getLanguageForSelection(), + enabledProviders = _providerRegistrationHandler.getProvidersForLanguageId(language.getId()); + + enabledProviders.some(function (item, index) { + if (item.provider.hasParameterHints(editor, lastChar)) { + sessionProvider = item.provider; + return true; + } + }); + + if (sessionProvider) { + request = sessionProvider.getParameterHints(explicit, onCursorActivity); + } + + if (request) { + request.done(function (parameterHint) { + var cm = editor._codeMirror, + pos = parameterHint.functionCallPos || editor.getCursorPos(); + + pos = cm.charCoords(pos); + formatHint(parameterHint); + + $hintContainer.show(); + positionHint(pos.left, pos.top, pos.bottom); + hintState.visible = true; + + sessionEditor = editor; + editor.on("cursorActivity.ParameterHinting", handleCursorActivity); + $deferredPopUp.resolveWith(null); + }).fail(function () { + hintState = {}; + }); + } + + return $deferredPopUp; + } + + /** + * Show the parameter the cursor is on in bold when the cursor moves. + * Dismiss the pop up when the cursor moves off the function. + */ + handleCursorActivity = function (event, editor) { + if (editor) { + popUpHint(editor, false, true); + } else { + dismissHint(); + } + }; + + /** + * Install function hint listeners. + * + * @param {Editor} editor - editor context on which to listen for + * changes + */ + function installListeners(editor) { + editor.on("keydown.ParameterHinting", function (event, editor, domEvent) { + if (domEvent.keyCode === KeyEvent.DOM_VK_ESCAPE) { + dismissHint(editor); + } + }).on("scroll.ParameterHinting", function () { + dismissHint(editor); + }) + .on("editorChange.ParameterHinting", _handleChange) + .on("keypress.ParameterHinting", _handleKeypressEvent); + } + + /** + * Clean up after installListeners() + * @param {!Editor} editor + */ + function uninstallListeners(editor) { + editor.off(".ParameterHinting"); + } + + function _handleKeypressEvent(jqEvent, editor, event) { + keyDownEditor = editor; + // Last inserted character, used later by handleChange + lastChar = String.fromCharCode(event.charCode); + } + + /** + * Start a new implicit hinting session, or update the existing hint list. + * Called by the editor after handleKeyEvent, which is responsible for setting + * the lastChar. + * + * @param {Event} event + * @param {Editor} editor + * @param {{from: Pos, to: Pos, text: Array, origin: string}} changeList + */ + function _handleChange(event, editor, changeList) { + if (lastChar && (lastChar === '(' || lastChar === ',') && editor === keyDownEditor) { + keyDownEditor = null; + popUpHint(editor); + } + } + + function activeEditorChangeHandler(event, current, previous) { + + if (previous) { + //Removing all old Handlers + previous.document + .off("languageChanged.ParameterHinting"); + uninstallListeners(previous); + } + + if (current) { + current.document + .on("languageChanged.ParameterHinting", function () { + // If current doc's language changed, reset our state by treating it as if the user switched to a + // different document altogether + uninstallListeners(current); + installListeners(current); + }); + installListeners(current); + } + } + + /** + * Show a parameter hint in its own pop-up. + * + */ + function handleShowParameterHint() { + var editor = EditorManager.getActiveEditor(); + // Pop up function hint + popUpHint(editor, true, false); + } + + AppInit.appReady(function () { + CommandManager.register(Strings.CMD_SHOW_PARAMETER_HINT, SHOW_PARAMETER_HINT_CMD_ID, handleShowParameterHint); + + // Add the menu items + var menu = Menus.getMenu(Menus.AppMenuBar.EDIT_MENU); + if (menu) { + menu.addMenuItem(SHOW_PARAMETER_HINT_CMD_ID, KeyboardPrefs.showParameterHint, Menus.AFTER, Commands.SHOW_CODE_HINTS); + } + // Create the function hint container + $hintContainer = $(hintContainerHTML).appendTo($("body")); + $hintContent = $hintContainer.find(".function-hint-content-new"); + activeEditorChangeHandler(null, EditorManager.getActiveEditor(), null); + + EditorManager.on("activeEditorChange", activeEditorChangeHandler); + + CommandManager.on("beforeExecuteCommand", function (event, commandId) { + if (commandId !== SHOW_PARAMETER_HINT_CMD_ID && + commandId !== Commands.SHOW_CODE_HINTS) { + dismissHint(); + } + }); + }); + + exports.registerHintProvider = registerHintProvider; + exports.removeHintProvider = removeHintProvider; +}); diff --git a/src/features/PriorityBasedRegistration.js b/src/features/PriorityBasedRegistration.js new file mode 100644 index 00000000000..1ad6214d48e --- /dev/null +++ b/src/features/PriorityBasedRegistration.js @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint-disable indent */ +define(function (require, exports, module) { + "use strict"; + + var PreferencesManager = require("preferences/PreferencesManager"); + + /** + * Comparator to sort providers from high to low priority + */ + function _providerSort(a, b) { + return b.priority - a.priority; + } + + + function RegistrationHandler() { + this._providers = { + "all": [] + }; + } + + /** + * The method by which a Provider registers its willingness to + * providing tooling feature for editors in a given language. + * + * @param {!Provider} provider + * The provider to be registered, described below. + * + * @param {!Array.} languageIds + * The set of language ids for which the provider is capable of + * providing tooling feature. If the special language id name "all" is included then + * the provider may be called for any language. + * + * @param {?number} priority + * Used to break ties among providers for a particular language. + * Providers with a higher number will be asked for tooling before those + * with a lower priority value. Defaults to zero. + */ + RegistrationHandler.prototype.registerProvider = function (providerInfo, languageIds, priority) { + var providerObj = { + provider: providerInfo, + priority: priority || 0 + }, + self = this; + + if (languageIds.indexOf("all") !== -1) { + // Ignore anything else in languageIds and just register for every language. This includes + // the special "all" language since its key is in the hintProviders map from the beginning. + var languageId; + for (languageId in self._providers) { + if (self._providers.hasOwnProperty(languageId)) { + self._providers[languageId].push(providerObj); + self._providers[languageId].sort(_providerSort); + } + } + } else { + languageIds.forEach(function (languageId) { + if (!self._providers[languageId]) { + // Initialize provider list with any existing all-language providers + self._providers[languageId] = Array.prototype.concat(self._providers.all); + } + self._providers[languageId].push(providerObj); + self._providers[languageId].sort(_providerSort); + }); + } + }; + + /** + * Remove a code hint provider + * @param {!CodeHintProvider} provider Code hint provider to remove + * @param {(string|Array.)=} targetLanguageId Optional set of + * language IDs for languages to remove the provider for. Defaults + * to all languages. + */ + RegistrationHandler.prototype.removeProvider = function (provider, targetLanguageId) { + var index, + providers, + targetLanguageIdArr, + self = this; + + if (Array.isArray(targetLanguageId)) { + targetLanguageIdArr = targetLanguageId; + } else if (targetLanguageId) { + targetLanguageIdArr = [targetLanguageId]; + } else { + targetLanguageIdArr = Object.keys(self._providers); + } + + targetLanguageIdArr.forEach(function (languageId) { + providers = self._providers[languageId]; + + for (index = 0; index < providers.length; index++) { + if (providers[index].provider === provider) { + providers.splice(index, 1); + break; + } + } + }); + }; + + + RegistrationHandler.prototype.getProvidersForLanguageId = function (languageId) { + var providers = this._providers[languageId] || this._providers.all; + + // Exclude providers that are explicitly disabled in the preferences. + // All providers that do not have their constructor + // names listed in the preferences are enabled by default. + return providers.filter(function (provider) { + var prefKey = "tooling." + provider.provider.constructor.name; + return PreferencesManager.get(prefKey) !== false; + }); + }; + + + exports.RegistrationHandler = RegistrationHandler; +}); diff --git a/src/htmlContent/parameter-hint-template.html b/src/htmlContent/parameter-hint-template.html new file mode 100644 index 00000000000..ffdd53d620b --- /dev/null +++ b/src/htmlContent/parameter-hint-template.html @@ -0,0 +1,4 @@ +
+
+
+
diff --git a/src/languageTools/BracketsToNodeInterface.js b/src/languageTools/BracketsToNodeInterface.js new file mode 100644 index 00000000000..71c12970218 --- /dev/null +++ b/src/languageTools/BracketsToNodeInterface.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-invalid-this: 0*/ +define(function (require, exports, module) { + "use strict"; + + function BracketsToNodeInterface(domain) { + this.domain = domain; + this.bracketsFn = {}; + + this._registerDataEvent(); + } + + BracketsToNodeInterface.prototype._messageHandler = function (evt, params) { + var methodName = params.method, + self = this; + + function _getErrorString(err) { + if (typeof err === "string") { + return err; + } else if (err && err.name && err.name === "Error") { + return err.message; + } + return "Error in executing " + methodName; + + } + + function _sendResponse(response) { + var responseParams = { + requestId: params.requestId, + params: response + }; + self.domain.exec("response", responseParams); + } + + function _sendError(err) { + var responseParams = { + requestId: params.requestId, + error: _getErrorString(err) + }; + self.domain.exec("response", responseParams); + } + + if (self.bracketsFn[methodName]) { + var method = self.bracketsFn[methodName]; + try { + var response = method.call(null, params.params); + if (params.respond && params.requestId) { + if (response.promise) { + response.done(function (result) { + _sendResponse(result); + }).fail(function (err) { + _sendError(err); + }); + } else { + _sendResponse(response); + } + } + } catch (err) { + if (params.respond && params.requestId) { + _sendError(err); + } + } + } + + }; + + + BracketsToNodeInterface.prototype._registerDataEvent = function () { + this.domain.on("data", this._messageHandler.bind(this)); + }; + + BracketsToNodeInterface.prototype.createInterface = function (methodName, isAsync) { + var self = this; + return function (params) { + var execEvent = isAsync ? "asyncData" : "data"; + var callObject = { + method: methodName, + params: params + }; + return self.domain.exec(execEvent, callObject); + }; + }; + + BracketsToNodeInterface.prototype.registerMethod = function (methodName, methodHandle) { + if (methodName && methodHandle && + typeof methodName === "string" && typeof methodHandle === "function") { + this.bracketsFn[methodName] = methodHandle; + } + }; + + BracketsToNodeInterface.prototype.registerMethods = function (methodList) { + var self = this; + methodList.forEach(function (methodObj) { + self.registerMethod(methodObj.methodName, methodObj.methodHandle); + }); + }; + + exports.BracketsToNodeInterface = BracketsToNodeInterface; +}); diff --git a/src/languageTools/ClientLoader.js b/src/languageTools/ClientLoader.js new file mode 100644 index 00000000000..c2fa615da6b --- /dev/null +++ b/src/languageTools/ClientLoader.js @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-console: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var ToolingInfo = JSON.parse(require("text!languageTools/ToolingInfo.json")), + NodeDomain = require("utils/NodeDomain"), + FileUtils = require("file/FileUtils"), + BracketsToNodeInterface = require("languageTools/BracketsToNodeInterface").BracketsToNodeInterface; + + //Register paths required for Language Client and also register default brackets capabilities. + var _bracketsPath = FileUtils.getNativeBracketsDirectoryPath(); + // The native directory path ends with either "test" or "src". + _bracketsPath = _bracketsPath.replace(/\/test$/, "/src"); // convert from "test" to "src" + + var _modulePath = FileUtils.getNativeModuleDirectoryPath(module), + _nodePath = "node/RegisterLanguageClientInfo", + _domainPath = [_bracketsPath, _modulePath, _nodePath].join("/"), + clientInfoDomain = new NodeDomain("LanguageClientInfo", _domainPath), + //Init node with Information required by Language Client + clientInfoLoadedPromise = clientInfoDomain.exec("initialize", _bracketsPath, ToolingInfo), + //Clients that have to be loaded once the LanguageClient info is successfully loaded on the + //node side. + pendingClientsToBeLoaded = []; + + //Attach success and failure function for the clientInfoLoadedPromise + clientInfoLoadedPromise.then(function () { + pendingClientsToBeLoaded.forEach(function (pendingClient) { + pendingClient.load(); + }); + }, function () { + console.log("Failed to Initialize LanguageClient Module Information."); + }); + + function syncPrefsWithDomain(languageToolsPrefs) { + if (clientInfoDomain) { + clientInfoDomain.exec("syncPreferences", languageToolsPrefs); + } + } + + function _createNodeDomain(domainName, domainPath) { + return new NodeDomain(domainName, domainPath); + } + + function loadLanguageClientDomain(clientName, domainPath) { + //generate a random hash name for the domain, this is the client id + var domainName = clientName, + result = $.Deferred(), + languageClientDomain = _createNodeDomain(domainName, domainPath); + + if (languageClientDomain) { + languageClientDomain.promise() + .done(function () { + console.log(domainPath + " domain successfully created"); + result.resolve(languageClientDomain); + }) + .fail(function (err) { + console.error(domainPath + " domain could not be created."); + result.reject(); + }); + } else { + console.error(domainPath + " domain could not be created."); + result.reject(); + } + + return result; + } + + function createNodeInterfaceForDomain(languageClientDomain) { + var nodeInterface = new BracketsToNodeInterface(languageClientDomain); + + return nodeInterface; + } + + function _clientLoader(clientName, clientFilePath, clientPromise) { + loadLanguageClientDomain(clientName, clientFilePath) + .then(function (languageClientDomain) { + var languageClientInterface = createNodeInterfaceForDomain(languageClientDomain); + + clientPromise.resolve({ + name: clientName, + interface: languageClientInterface + }); + }, clientPromise.reject); + } + + function initiateLanguageClient(clientName, clientFilePath) { + var result = $.Deferred(); + + //Only load clients after the LanguageClient Info has been initialized + if (clientInfoLoadedPromise.state() === "pending") { + var pendingClient = { + load: _clientLoader.bind(null, clientName, clientFilePath, result) + }; + pendingClientsToBeLoaded.push(pendingClient); + } else { + _clientLoader(clientName, clientFilePath, result); + } + + return result; + } + + exports.initiateLanguageClient = initiateLanguageClient; + exports.syncPrefsWithDomain = syncPrefsWithDomain; +}); diff --git a/src/languageTools/DefaultEventHandlers.js b/src/languageTools/DefaultEventHandlers.js new file mode 100644 index 00000000000..e544ae1b557 --- /dev/null +++ b/src/languageTools/DefaultEventHandlers.js @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint-disable indent */ +/* eslint no-console: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageManager = require("language/LanguageManager"), + ProjectManager = require("project/ProjectManager"), + PathConverters = require("languageTools/PathConverters"); + + function EventPropagationProvider(client) { + this.client = client; + this.previousProject = ""; + this.currentProject = ProjectManager.getProjectRoot(); + } + + EventPropagationProvider.prototype._sendDocumentOpenNotification = function (languageId, doc) { + if (!this.client) { + return; + } + + if (this.client._languages.includes(languageId)) { + this.client.notifyTextDocumentOpened({ + languageId: languageId, + filePath: (doc.file._path || doc.file.fullPath), + fileContent: doc.getText() + }); + } + }; + + EventPropagationProvider.prototype.handleActiveEditorChange = function (event, current, previous) { + var self = this; + + if (!this.client) { + return; + } + + if (previous) { + previous.document + .off("languageChanged.language-tools"); + var previousLanguageId = LanguageManager.getLanguageForPath(previous.document.file.fullPath).getId(); + if (this.client._languages.includes(previousLanguageId)) { + this.client.notifyTextDocumentClosed({ + filePath: (previous.document.file._path || previous.document.file.fullPath) + }); + } + } + if (current) { + var currentLanguageId = LanguageManager.getLanguageForPath(current.document.file.fullPath).getId(); + current.document + .on("languageChanged.language-tools", function () { + var languageId = LanguageManager.getLanguageForPath(current.document.file.fullPath).getId(); + self._sendDocumentOpenNotification(languageId, current.document); + }); + self._sendDocumentOpenNotification(currentLanguageId, current.document); + } + }; + + EventPropagationProvider.prototype.handleProjectOpen = function (event, directory) { + if (!this.client) { + return; + } + + this.currentProject = directory.fullPath; + + this.client.notifyProjectRootsChanged({ + foldersAdded: [this.currentProject], + foldersRemoved: [this.previousProject] + }); + }; + + EventPropagationProvider.prototype.handleProjectClose = function (event, directory) { + if (!this.client) { + return; + } + + this.previousProject = directory.fullPath; + }; + + EventPropagationProvider.prototype.handleDocumentDirty = function (event, doc) { + if (!this.client) { + return; + } + + if (!doc.isDirty) { + var docLanguageId = LanguageManager.getLanguageForPath(doc.file.fullPath).getId(); + if (this.client._languages.includes(docLanguageId)) { + this.client.notifyTextDocumentSave({ + filePath: (doc.file._path || doc.file.fullPath) + }); + } + } + }; + + EventPropagationProvider.prototype.handleDocumentChange = function (event, doc, changeList) { + if (!this.client) { + return; + } + + var docLanguageId = LanguageManager.getLanguageForPath(doc.file.fullPath).getId(); + if (this.client._languages.includes(docLanguageId)) { + this.client.notifyTextDocumentChanged({ + filePath: (doc.file._path || doc.file.fullPath), + fileContent: doc.getText() + }); + } + }; + + EventPropagationProvider.prototype.handleDocumentRename = function (event, oldName, newName) { + if (!this.client) { + return; + } + + var oldDocLanguageId = LanguageManager.getLanguageForPath(oldName).getId(); + if (this.client._languages.includes(oldDocLanguageId)) { + this.client.notifyTextDocumentClosed({ + filePath: oldName + }); + } + + var newDocLanguageId = LanguageManager.getLanguageForPath(newName).getId(); + if (this.client._languages.includes(newDocLanguageId)) { + this.client.notifyTextDocumentOpened({ + filePath: newName + }); + } + }; + + EventPropagationProvider.prototype.handleAppClose = function (event) { + //Also handles Reload with Extensions + if (!this.client) { + return; + } + + this.client.stop(); + }; + + function handleProjectFoldersRequest(event) { + var projectRoot = ProjectManager.getProjectRoot(), + workspaceFolders = [projectRoot]; + + workspaceFolders = PathConverters.convertToWorkspaceFolders(workspaceFolders); + + return $.Deferred().resolve(workspaceFolders); + }; + + EventPropagationProvider.prototype.registerClientForEditorEvent = function () { + if (this.client) { + var handleActiveEditorChange = this.handleActiveEditorChange.bind(this), + handleProjectOpen = this.handleProjectOpen.bind(this), + handleProjectClose = this.handleProjectClose.bind(this), + handleDocumentDirty = this.handleDocumentDirty.bind(this), + handleDocumentChange = this.handleDocumentChange.bind(this), + handleDocumentRename = this.handleDocumentRename.bind(this), + handleAppClose = this.handleAppClose.bind(this); + + this.client.addOnEditorChangeHandler(handleActiveEditorChange); + this.client.addOnProjectOpenHandler(handleProjectOpen); + this.client.addBeforeProjectCloseHandler(handleProjectClose); + this.client.addOnDocumentDirtyFlagChangeHandler(handleDocumentDirty); + this.client.addOnDocumentChangeHandler(handleDocumentChange); + this.client.addOnFileRenameHandler(handleDocumentRename); + this.client.addBeforeAppClose(handleAppClose); + this.client.onProjectFoldersRequest(handleProjectFoldersRequest); + } else { + console.log("No client provided for event propagation"); + } + }; + + exports.EventPropagationProvider = EventPropagationProvider; +}); diff --git a/src/languageTools/DefaultProviders.js b/src/languageTools/DefaultProviders.js new file mode 100644 index 00000000000..d5cc5ad8a9b --- /dev/null +++ b/src/languageTools/DefaultProviders.js @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global Map*/ +/* eslint-disable indent */ +/* eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var _ = brackets.getModule("thirdparty/lodash"); + + var EditorManager = require('editor/EditorManager'), + ExtensionUtils = require("utils/ExtensionUtils"), + CommandManager = require("command/CommandManager"), + Commands = require("command/Commands"), + TokenUtils = require("utils/TokenUtils"), + StringMatch = require("utils/StringMatch"), + CodeInspection = require("language/CodeInspection"), + PathConverters = require("languageTools/PathConverters"), + matcher = new StringMatch.StringMatcher({ + preferPrefixMatches: true + }); + + ExtensionUtils.loadStyleSheet(module, "styles/default_provider_style.css"); + + function CodeHintsProvider(client) { + this.client = client; + this.query = ""; + } + + function formatTypeDataForToken($hintObj, token) { + $hintObj.addClass('brackets-hints-with-type-details'); + if (token.detail) { + if (token.detail.trim() !== '?') { + if (token.detail.length < 30) { + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("brackets-hints-type-details"); + } + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("hint-description"); + } + } else { + if (token.keyword) { + $('keyword').appendTo($hintObj).addClass("brackets-hints-keyword"); + } + } + if (token.documentation) { + $hintObj.attr('title', token.documentation); + $('').text(token.documentation.trim()).appendTo($hintObj).addClass("hint-doc"); + } + } + + function filterWithQueryAndMatcher(hints, query) { + var matchResults = $.map(hints, function (hint) { + var searchResult = matcher.match(hint.label, query); + if (searchResult) { + for (var key in hint) { + searchResult[key] = hint[key]; + } + } + + return searchResult; + }); + + return matchResults; + } + + CodeHintsProvider.prototype.hasHints = function (editor, implicitChar) { + if (!this.client) { + return false; + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.completionProvider) { + return false; + } + + return true; + }; + + CodeHintsProvider.prototype.getHints = function (implicitChar) { + if (!this.client) { + return null; + } + + var editor = EditorManager.getActiveEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + $deferredHints = $.Deferred(), + self = this; + + this.client.requestHints({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + var context = TokenUtils.getInitialContext(editor._codeMirror, pos), + hints = []; + + self.query = context.token.string.slice(0, context.pos.ch - context.token.start); + if (msgObj) { + var res = msgObj.items, + filteredHints = filterWithQueryAndMatcher(res, self.query); + + StringMatch.basicMatchSort(filteredHints); + filteredHints.forEach(function (element) { + var $fHint = $("") + .addClass("brackets-hints"); + + if (element.stringRanges) { + element.stringRanges.forEach(function (item) { + if (item.matched) { + $fHint.append($("") + .append(_.escape(item.text)) + .addClass("matched-hint")); + } else { + $fHint.append(_.escape(item.text)); + } + }); + } else { + $fHint.text(element.label); + } + + $fHint.data("token", element); + formatTypeDataForToken($fHint, element); + hints.push($fHint); + }); + } + + $deferredHints.resolve({ + "hints": hints + }); + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + CodeHintsProvider.prototype.insertHint = function ($hint) { + var editor = EditorManager.getActiveEditor(), + cursor = editor.getCursorPos(), + token = $hint.data("token"), + txt = null, + query = this.query, + start = { + line: cursor.line, + ch: cursor.ch - query.length + }, + + end = { + line: cursor.line, + ch: cursor.ch + }; + + txt = token.label; + if (token.textEdit && token.textEdit.newText) { + txt = token.textEdit.newText; + start = { + line: token.textEdit.range.start.line, + ch: token.textEdit.range.start.character + }; + end = { + line: token.textEdit.range.end.line, + ch: token.textEdit.range.end.character + }; + } + + if (editor) { + editor.document.replaceRange(txt, start, end); + } + // Return false to indicate that another hinting session is not needed + return false; + }; + + function ParameterHintsProvider(client) { + this.client = client; + } + + ParameterHintsProvider.prototype.hasParameterHints = function (editor, implicitChar) { + if (!this.client) { + return false; + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.signatureHelpProvider) { + return false; + } + + return true; + }; + + ParameterHintsProvider.prototype.getParameterHints = function () { + if (!this.client) { + return null; + } + + var editor = EditorManager.getActiveEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + $deferredHints = $.Deferred(); + + this.client.requestParameterHints({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + let paramList = []; + let label; + let activeParameter; + if (msgObj) { + let res; + res = msgObj.signatures; + activeParameter = msgObj.activeParameter; + if (res && res.length) { + res.forEach(function (element) { + label = element.documentation; + let param = element.parameters; + param.forEach(ele => { + paramList.push({ + label: ele.label, + documentation: ele.documentation + }); + }); + }); + + $deferredHints.resolve({ + parameters: paramList, + currentIndex: activeParameter, + functionDocumentation: label + }); + } else { + $deferredHints.reject(); + } + } else { + $deferredHints.reject(); + } + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + /** + * Utility function to make the jump + * @param {Object} curPos - target postion for the cursor after the jump + */ + function setJumpPosition(curPos) { + EditorManager.getCurrentFullEditor().setCursorPos(curPos.line, curPos.ch, true); + } + + function JumpToDefProvider(client) { + this.client = client; + } + + JumpToDefProvider.prototype.canJumpToDef = function (editor, implicitChar) { + if (!this.client) { + return false; + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.definitionProvider) { + return false; + } + + return true; + }; + + /** + * Method to handle jump to definition feature. + */ + JumpToDefProvider.prototype.doJumpToDef = function () { + if (!this.client) { + return null; + } + + var editor = EditorManager.getFocusedEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + docPathUri = PathConverters.pathToUri(docPath), + $deferredHints = $.Deferred(); + + this.client.gotoDefinition({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + //For Older servers + if (Array.isArray(msgObj)) { + msgObj = msgObj[msgObj.length - 1]; + } + + if (msgObj && msgObj.range) { + var docUri = msgObj.uri, + startCurPos = {}; + startCurPos.line = msgObj.range.start.line; + startCurPos.ch = msgObj.range.start.character; + + if (docUri !== docPathUri) { + let documentPath = PathConverters.uriToPath(docUri); + CommandManager.execute(Commands.FILE_OPEN, { + fullPath: documentPath + }) + .done(function () { + setJumpPosition(startCurPos); + $deferredHints.resolve(); + }); + } else { //definition is in current document + setJumpPosition(startCurPos); + $deferredHints.resolve(); + } + } + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + function LintingProvider() { + this._results = new Map(); + } + + LintingProvider.prototype.clearExistingResults = function (filePath) { + var filePathProvided = !!filePath; + + if (filePathProvided) { + this._results.delete(filePath); + } else { + //clear all results + this._results.clear(); + } + }; + + /** + * Publish the diagnostics information related to current document + * @param {Object} msgObj - json object containing information associated with 'textDocument/publishDiagnostics' notification from server + */ + LintingProvider.prototype.setInspectionResults = function (msgObj) { + let diagnostics = msgObj.diagnostics, + filePath = PathConverters.uriToPath(msgObj.uri), + errors = []; + + errors = diagnostics.map(function (obj) { + return { + pos: { + line: obj.range.start.line, + ch: obj.range.start.character + }, + message: obj.message, + type: (obj.severity === 1 ? CodeInspection.Type.ERROR : (obj.severity === 2 ? CodeInspection.Type.WARNING : CodeInspection.Type.META)) + }; + }); + + this._results.set(filePath, { + errors: errors + }); + }; + + LintingProvider.prototype.getInspectionResults = function (fileText, filePath) { + return this._results.get(filePath); + }; + + exports.CodeHintsProvider = CodeHintsProvider; + exports.ParameterHintsProvider = ParameterHintsProvider; + exports.JumpToDefProvider = JumpToDefProvider; + exports.LintingProvider = LintingProvider; +}); diff --git a/src/languageTools/LanguageClient/Connection.js b/src/languageTools/LanguageClient/Connection.js new file mode 100644 index 00000000000..47aa9c6747d --- /dev/null +++ b/src/languageTools/LanguageClient/Connection.js @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports */ +/*eslint no-console: 0*/ +/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ +(function () { + "use strict"; + + var protocol = require("vscode-languageserver-protocol"); + + var Actions = { + OnClose: { + Stop: 0, + Restart: 1 + }, + OnError: { + Ignore: 0, + Stop: 1 + } + }; + + function ActionController() { + this.restartsTimes = []; + } + + ActionController.prototype.getOnErrorAction = function (errorData) { + var errorCount = errorData[2]; + + if (errorCount <= 3) { + return Actions.OnError.Ignore; + } + + return Actions.OnError.Restart; + }; + + ActionController.prototype.getOnCloseAction = function () { + var currentTime = Date.now(); + this.restartsTimes.push(currentTime); + + var numRestarts = this.restartsTimes.length; + if (numRestarts < 5) { + return Actions.OnClose.Restart; + } + + var timeBetweenFiveRestarts = this.restartsTimes[numRestarts - 1] - this.restartsTimes[0]; + if (timeBetweenFiveRestarts <= 3 * 60 * 1000) { //3 minutes + return Actions.OnClose.Stop; + } + + this.restartsTimes.shift(); + return Actions.OnClose.Restart; + }; + + function _getOnCloseHandler(connection, actionController, restartLanguageClient) { + return function () { + try { + if (connection) { + connection.dispose(); + } + } catch (error) {} + + var action = Actions.OnClose.Stop; + try { + action = actionController.getOnCloseAction(); + } catch (error) {} + + + if (action === Actions.OnClose.Restart) { + restartLanguageClient(); + } + }; + } + + function _getOnErrorHandler(actionController, stopLanguageClient) { + return function (errorData) { + var action = actionController.getOnErrorAction(errorData); + + if (action === Actions.OnError.Stop) { + stopLanguageClient(); + } + }; + } + + function Logger() {} + + Logger.prototype.error = function (message) { + console.error(message); + }; + Logger.prototype.warn = function (message) { + console.warn(message); + }; + Logger.prototype.info = function (message) { + console.info(message); + }; + Logger.prototype.log = function (message) { + console.log(message); + }; + + function createConnection(reader, writer, restartLanguageClient, stopLanguageClient) { + var logger = new Logger(), + actionController = new ActionController(), + connection = protocol.createProtocolConnection(reader, writer, logger), + errorHandler = _getOnErrorHandler(actionController, stopLanguageClient), + closeHandler = _getOnCloseHandler(connection, actionController, restartLanguageClient); + + connection.onError(errorHandler); + connection.onClose(closeHandler); + + return connection; + } + + exports.createConnection = createConnection; +}()); diff --git a/src/languageTools/LanguageClient/LanguageClient.js b/src/languageTools/LanguageClient/LanguageClient.js new file mode 100644 index 00000000000..a8e4888ed0d --- /dev/null +++ b/src/languageTools/LanguageClient/LanguageClient.js @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports, Promise, LanguageClientInfo */ +/*eslint no-console: 0*/ +/*eslint strict: ["error", "global"]*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +"use strict"; + +var ProtocolAdapter = require("./ProtocolAdapter"), + ServerUtils = require("./ServerUtils"), + Connection = require("./Connection"), + NodeToBracketsInterface = require("./NodeToBracketsInterface").NodeToBracketsInterface, + ToolingInfo = LanguageClientInfo.toolingInfo, + MESSAGE_TYPE = { + BRACKETS: "brackets", + SERVER: "server" + }; + +function validateHandler(handler) { + var retval = false; + + if (handler && typeof handler === "function") { + retval = true; + } else { + console.warn("Handler validation failed. Handler should be of type 'function'. Provided handler is of type :", typeof handler); + } + + return retval; +} + +function LanguageClient(clientName, domainManager, options) { + this._clientName = clientName; + this._bracketsInterface = null; + this._notifyBrackets = null; + this._requestBrackets = null; + this._connection = null, + this._startUpParams = null, //_projectRoot, capabilties, workspaceFolders etc. + this._initialized = false, + this._onRequestHandler = {}; + this._onNotificationHandlers = {}; + this._options = options || null; + + + this._init(domainManager); +} + + +LanguageClient.prototype._createConnection = function () { + if (!this._options || !this._options.serverOptions) { + return Promise.reject("No valid options provided for client :", this._clientName); + } + + var restartLanguageClient = this.start.bind(this), + stopLanguageClient = this.stop.bind(this); + + var serverOptions = this._options.serverOptions; + return ServerUtils.startServerAndGetConnectionArgs(serverOptions) + .then(function (connectionArgs) { + return Connection.createConnection(connectionArgs.reader, connectionArgs.writer, restartLanguageClient, stopLanguageClient); + }).catch(function (err) { + console.error("Couldn't establish connection", err); + }); +}; + +LanguageClient.prototype.setOptions = function (options) { + if (options && typeof options === "object") { + this._options = options; + } else { + console.error("Invalid options provided for client :", this._clientName); + } +}; + +LanguageClient.prototype.start = function (params) { + var self = this; + + //Check to see if a connection to a language server already exists. + if (self._connection) { + return Promise.resolve(true); + } + + self._connection = null; + self._startUpParams = params || self._startUpParams; + + //We default to standard capabilties + if (!self._startUpParams.capabilities) { + self._startUpParams.capabilities = LanguageClientInfo.defaultBracketsCapabilities; + } + + return self._createConnection() + .then(function (connection) { + connection.listen(); + self._connection = connection; + + return ProtocolAdapter.initialize(connection, self._startUpParams); + }).then(function (result) { + self._initialized = result; + ProtocolAdapter.attachOnNotificationHandlers(self._connection, self._notifyBrackets); + ProtocolAdapter.attachOnRequestHandlers(self._connection, self._requestBrackets); + ProtocolAdapter.initialized(self._connection); + return result; + }).catch(function (error) { + console.error('Starting client failed because :', error); + console.error('Couldn\'t start client :', self._clientName); + + return error; + }); +}; + +LanguageClient.prototype.stop = function () { + var self = this; + + self._initialized = false; + if (!self._connection) { + return Promise.resolve(true); + } + + + return ProtocolAdapter.shutdown(self._connection).then(function () { + ProtocolAdapter.exit(self._connection); + self._connection.dispose(); + self._connection = null; + }); +}; + +LanguageClient.prototype.request = function (params) { + var messageParams = params.params; + if (messageParams && messageParams.messageType === MESSAGE_TYPE.BRACKETS) { + if (!messageParams.type) { + console.log("Invalid brackets request"); + return Promise.reject(); + } + + var requestHandler = this._onRequestHandler[messageParams.type]; + if(validateHandler(requestHandler)) { + return requestHandler.call(null, messageParams.params); + } + console.log("No handler provided for brackets request type : ", messageParams.type); + return Promise.reject(); + } + return ProtocolAdapter.processRequest(this._connection, params); + +}; + +LanguageClient.prototype.notify = function (params) { + var messageParams = params.params; + if (messageParams && messageParams.messageType === MESSAGE_TYPE.BRACKETS) { + if (!messageParams.type) { + console.log("Invalid brackets notification"); + return; + } + + var notificationHandlers = this._onNotificationHandlers[messageParams.type]; + if(notificationHandlers && Array.isArray(notificationHandlers) && notificationHandlers.length) { + notificationHandlers.forEach(function (handler) { + if(validateHandler(handler)) { + handler.call(null, messageParams.params); + } + }); + } else { + console.log("No handlers provided for brackets notification type : ", messageParams.type); + } + } else { + ProtocolAdapter.processNotification(this._connection, params); + } +}; + +LanguageClient.prototype.addOnRequestHandler = function (type, handler) { + if (validateHandler(handler)) { + this._onRequestHandler[type] = handler; + } +}; + +LanguageClient.prototype.addOnNotificationHandler = function (type, handler) { + if (validateHandler(handler)) { + if (!this._onNotificationHandlers[type]) { + this._onNotificationHandlers[type] = []; + } + + this._onNotificationHandlers[type].push(handler); + } +}; + +LanguageClient.prototype._init = function (domainManager) { + this._bracketsInterface = new NodeToBracketsInterface(domainManager, this._clientName); + + //Expose own methods for interfaceing. All these are async except notify. + this._bracketsInterface.registerMethods([ + { + methodName: ToolingInfo.LANGUAGE_SERVICE.START, + methodHandle: this.start.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.STOP, + methodHandle: this.stop.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.REQUEST, + methodHandle: this.request.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.NOTIFY, + methodHandle: this.notify.bind(this) + } + ]); + + //create function interfaces for Brackets + this._notifyBrackets = this._bracketsInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.NOTIFY); + this._requestBrackets = this._bracketsInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.REQUEST, true); +}; + +exports.LanguageClient = LanguageClient; diff --git a/src/languageTools/LanguageClient/NodeToBracketsInterface.js b/src/languageTools/LanguageClient/NodeToBracketsInterface.js new file mode 100644 index 00000000000..80d85e96c12 --- /dev/null +++ b/src/languageTools/LanguageClient/NodeToBracketsInterface.js @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global require, Promise, exports*/ +/*eslint no-invalid-this: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +(function () { + + "use strict"; + + var EventEmitter = require("events"), + bracketsEventHandler = new EventEmitter(); + + /** https://gist.github.com/LeverOne/1308368 */ + /*eslint-disable */ + function _generateUUID() { + var result, + numericSeed; + for ( + result = numericSeed = ''; + numericSeed++ < 36; + result += numericSeed * 51 & 52 ? (numericSeed ^ 15 ? 8 ^ Math.random() * (numericSeed ^ 20 ? 16 : 4) : 4).toString(16) : '-' + ); + + return result; + } + /*eslint-enable */ + + function NodeToBracketsInterface(domainManager, domainName) { + this.domainManager = domainManager; + this.domainName = domainName; + this.nodeFn = {}; + + this._registerDataEvents(domainManager, domainName); + } + + NodeToBracketsInterface.prototype.processRequest = function (params) { + var methodName = params.method; + if (this.nodeFn[methodName]) { + var method = this.nodeFn[methodName]; + return method.call(null, params.params); + } + }; + + NodeToBracketsInterface.prototype.processAsyncRequest = function (params, resolver) { + var methodName = params.method; + if (this.nodeFn[methodName]) { + var method = this.nodeFn[methodName]; + method.call(null, params.params) //The Async function should return a promise + .then(function (result) { + resolver(null, result); + }).catch(function (err) { + resolver(err, null); + }); + } + }; + + NodeToBracketsInterface.prototype.processResponse = function (params) { + if (params.requestId) { + if (params.error) { + bracketsEventHandler.emit(params.requestId, params.error); + } else { + bracketsEventHandler.emit(params.requestId, false, params.params); + } + } else { + bracketsEventHandler.emit(params.requestId, "error"); + } + }; + + NodeToBracketsInterface.prototype.createInterface = function (methodName, respond) { + var self = this; + return function (params) { + var callObject = { + method: methodName, + params: params + }; + + var retval = undefined; + if (respond) { + var requestId = _generateUUID(); + + callObject["respond"] = true; + callObject["requestId"] = requestId; + + self.domainManager.emitEvent(self.domainName, "data", callObject); + + retval = new Promise(function (resolve, reject) { + bracketsEventHandler.once(requestId, function (err, response) { + if (err) { + reject(err); + } else { + resolve(response); + } + }); + }); + } else { + self.domainManager.emitEvent(self.domainName, "data", callObject); + } + return retval; + }; + }; + + NodeToBracketsInterface.prototype.registerMethod = function (methodName, methodHandle) { + var self = this; + if (methodName && methodHandle && + typeof methodName === "string" && typeof methodHandle === "function") { + self.nodeFn[methodName] = methodHandle; + } + }; + + NodeToBracketsInterface.prototype.registerMethods = function (methodList) { + var self = this; + methodList.forEach(function (methodObj) { + self.registerMethod(methodObj.methodName, methodObj.methodHandle); + }); + }; + + NodeToBracketsInterface.prototype._registerDataEvents = function (domainManager, domainName) { + if (!domainManager.hasDomain(domainName)) { + domainManager.registerDomain(domainName, { + major: 0, + minor: 1 + }); + } + + domainManager.registerCommand( + domainName, + "data", + this.processRequest.bind(this), + false, + "Receives sync request from brackets", + [ + { + name: "params", + type: "object", + description: "json object containing message info" + } + ], + [] + ); + + domainManager.registerCommand( + domainName, + "response", + this.processResponse.bind(this), + false, + "Receives response from brackets for an earlier request", + [ + { + name: "params", + type: "object", + description: "json object containing message info" + } + ], + [] + ); + + domainManager.registerCommand( + domainName, + "asyncData", + this.processAsyncRequest.bind(this), + true, + "Receives async call request from brackets", + [ + { + name: "params", + type: "object", + description: "json object containing message info" + }, + { + name: "resolver", + type: "function", + description: "callback required to resolve the async request" + } + ], + [] + ); + + domainManager.registerEvent( + domainName, + "data", + [ + { + name: "params", + type: "object", + description: "json object containing message info to pass to brackets" + } + ] + ); + }; + + exports.NodeToBracketsInterface = NodeToBracketsInterface; +}()); diff --git a/src/languageTools/LanguageClient/ProtocolAdapter.js b/src/languageTools/LanguageClient/ProtocolAdapter.js new file mode 100644 index 00000000000..e9eabb35aaa --- /dev/null +++ b/src/languageTools/LanguageClient/ProtocolAdapter.js @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global LanguageClientInfo*/ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint no-fallthrough: 0*/ +"use strict"; + +var protocol = require("vscode-languageserver-protocol"), + Utils = require("./Utils"), + ToolingInfo = LanguageClientInfo.toolingInfo, + MESSAGE_FORMAT = { + BRACKETS: "brackets", + LSP: "lsp" + }; + +function _constructParamsAndRelay(relay, type, params) { + var _params = null, + handler = null; + + //Check for param object format. We won't change anything if the object is preformatted. + if (params.format === MESSAGE_FORMAT.LSP) { + params.format = undefined; + _params = JSON.parse(JSON.stringify(params)); + } + + switch (type) { + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST: + return sendCustomRequest(relay, params.type, params.params); + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION: + { + sendCustomNotification(relay, params.type, params.params); + break; + } + case ToolingInfo.SERVICE_REQUESTS.SHOW_SELECT_MESSAGE: + case ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST: + case ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST: + case ToolingInfo.SERVICE_REQUESTS.PROJECT_FOLDERS_REQUEST: + { + _params = { + type: type, + params: params + }; + return relay(_params); + } + case ToolingInfo.SERVICE_NOTIFICATIONS.SHOW_MESSAGE: + case ToolingInfo.SERVICE_NOTIFICATIONS.LOG_MESSAGE: + case ToolingInfo.SERVICE_NOTIFICATIONS.TELEMETRY: + case ToolingInfo.SERVICE_NOTIFICATIONS.DIAGNOSTICS: + { + _params = { + type: type, + params: params + }; + relay(_params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath), + languageId: params.languageId, + version: 1, + text: params.fileContent + } + }; + didOpenTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath), + version: 1 + }, + contentChanges: [{ + text: params.fileContent + }] + }; + didChangeTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED: + { + if (!_params) { + _params = { + textDocument: { + uri: Utils.pathToUri(params.filePath) + } + }; + + if (params.fileContent) { + _params['text'] = params.fileContent; + } + } + didSaveTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CLOSED: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + } + }; + + didCloseTextDocument(relay, _params); + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED: + { + var foldersAdded = params.foldersAdded || [], + foldersRemoved = params.foldersRemoved || []; + + foldersAdded = Utils.convertToWorkspaceFolders(foldersAdded); + foldersRemoved = Utils.convertToWorkspaceFolders(foldersRemoved); + + _params = _params || { + event: { + added: foldersAdded, + removed: foldersRemoved + } + }; + didChangeWorkspaceFolders(relay, _params); + break; + } + case ToolingInfo.FEATURES.CODE_HINTS: + handler = completion; + case ToolingInfo.FEATURES.PARAMETER_HINTS: + handler = handler || signatureHelp; + case ToolingInfo.FEATURES.JUMP_TO_DECLARATION: + handler = handler || gotoDeclaration; + case ToolingInfo.FEATURES.JUMP_TO_DEFINITION: + handler = handler || gotoDefinition; + case ToolingInfo.FEATURES.JUMP_TO_IMPL: + { + handler = handler || gotoImplementation; + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + }, + position: Utils.convertToLSPPosition(params.cursorPos) + }; + + return handler(relay, _params); + } + case ToolingInfo.FEATURES.CODE_HINT_INFO: + { + return completionItemResolve(relay, params); + } + case ToolingInfo.FEATURES.FIND_REFERENCES: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + }, + position: Utils.convertToLSPPosition(params.cursorPos), + context: { + includeDeclaration: params.includeDeclaration + } + }; + + return findReferences(relay, _params); + } + case ToolingInfo.FEATURES.DOCUMENT_SYMBOLS: + { + _params = _params || { + textDocument: { + uri: Utils.pathToUri(params.filePath) + } + }; + + return documentSymbol(relay, _params); + } + case ToolingInfo.FEATURES.PROJECT_SYMBOLS: + { + _params = _params || { + query: params.query + }; + + return workspaceSymbol(relay, _params); + } + } +} + +/** For custom messages */ +function onCustom(connection, type, handler) { + connection.onNotification(type, handler); +} + +function sendCustomRequest(connection, type, params) { + return connection.sendRequest(type, params); +} + +function sendCustomNotification(connection, type, params) { + connection.sendNotification(type, params); +} + +/** For Notification messages */ +function didOpenTextDocument(connection, params) { + connection.sendNotification(protocol.DidOpenTextDocumentNotification.type, params); +} + +function didChangeTextDocument(connection, params) { + connection.sendNotification(protocol.DidChangeTextDocumentNotification.type, params); +} + +function didCloseTextDocument(connection, params) { + connection.sendNotification(protocol.DidCloseTextDocumentNotification.type, params); +} + +function didSaveTextDocument(connection, params) { + connection.sendNotification(protocol.DidSaveTextDocumentNotification.type, params); +} + +function didChangeWorkspaceFolders(connection, params) { + connection.sendNotification(protocol.DidChangeWorkspaceFoldersNotification.type, params); +} + +/** For Request messages */ +function completion(connection, params) { + return connection.sendRequest(protocol.CompletionRequest.type, params); +} + +function completionItemResolve(connection, params) { + return connection.sendRequest(protocol.CompletionResolveRequest.type, params); +} + +function signatureHelp(connection, params) { + return connection.sendRequest(protocol.SignatureHelpRequest.type, params); +} + +function gotoDefinition(connection, params) { + return connection.sendRequest(protocol.DefinitionRequest.type, params); +} + +function gotoDeclaration(connection, params) { + return connection.sendRequest(protocol.DeclarationRequest.type, params); +} + +function gotoImplementation(connection, params) { + return connection.sendRequest(protocol.ImplementationRequest.type, params); +} + +function findReferences(connection, params) { + return connection.sendRequest(protocol.ReferencesRequest.type, params); +} + +function documentSymbol(connection, params) { + return connection.sendRequest(protocol.DocumentSymbolRequest.type, params); +} + +function workspaceSymbol(connection, params) { + return connection.sendRequest(protocol.WorkspaceSymbolRequest.type, params); +} + +/** + * Server commands + */ +function initialize(connection, params) { + var rootPath = params.rootPath, + workspaceFolders = params.rootPaths; + + if(!rootPath && workspaceFolders && Array.isArray(workspaceFolders)) { + rootPath = workspaceFolders[0]; + } + + if (!workspaceFolders) { + workspaceFolders = [rootPath]; + } + + if (workspaceFolders.length) { + workspaceFolders = Utils.convertToWorkspaceFolders(workspaceFolders); + } + + var _params = { + rootPath: rootPath, + rootUri: Utils.pathToUri(rootPath), + processId: process.pid, + capabilities: params.capabilities, + workspaceFolders: workspaceFolders + }; + + return connection.sendRequest(protocol.InitializeRequest.type, _params); +} + +function initialized(connection) { + connection.sendNotification(protocol.InitializedNotification.type); +} + +function shutdown(connection) { + return connection.sendRequest(protocol.ShutdownRequest.type); +} + +function exit(connection) { + connection.sendNotification(protocol.ExitNotification.type); +} + +function processRequest(connection, message) { + return _constructParamsAndRelay(connection, message.type, message.params); +} + +function processNotification(connection, message) { + _constructParamsAndRelay(connection, message.type, message.params); +} + +function attachOnNotificationHandlers(connection, handler) { + function _callbackFactory(type) { + switch (type) { + case protocol.ShowMessageNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.SHOW_MESSAGE); + case protocol.LogMessageNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.LOG_MESSAGE); + case protocol.TelemetryEventNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.TELEMETRY); + case protocol.PublishDiagnosticsNotification.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_NOTIFICATIONS.DIAGNOSTICS); + } + } + + connection.onNotification(protocol.ShowMessageNotification.type, _callbackFactory(protocol.ShowMessageNotification.type)); + connection.onNotification(protocol.LogMessageNotification.type, _callbackFactory(protocol.LogMessageNotification.type)); + connection.onNotification(protocol.TelemetryEventNotification.type, _callbackFactory(protocol.TelemetryEventNotification.type)); + connection.onNotification(protocol.PublishDiagnosticsNotification.type, _callbackFactory(protocol.PublishDiagnosticsNotification.type)); + connection.onNotification(function (type, params) { + var _params = { + type: type, + params: params + }; + handler(_params); + }); +} + +function attachOnRequestHandlers(connection, handler) { + function _callbackFactory(type) { + switch (type) { + case protocol.ShowMessageRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.SHOW_SELECT_MESSAGE); + case protocol.RegistrationRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST); + case protocol.UnregistrationRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST); + case protocol.WorkspaceFoldersRequest.type: + return _constructParamsAndRelay.bind(null, handler, ToolingInfo.SERVICE_REQUESTS.PROJECT_FOLDERS_REQUEST); + } + } + + connection.onRequest(protocol.ShowMessageRequest.type, _callbackFactory(protocol.ShowMessageRequest.type)); + connection.onRequest(protocol.RegistrationRequest.type, _callbackFactory(protocol.RegistrationRequest.type)); + // See https://github.com/Microsoft/vscode-languageserver-node/issues/199 + connection.onRequest("client/registerFeature", _callbackFactory(protocol.RegistrationRequest.type)); + connection.onRequest(protocol.UnregistrationRequest.type, _callbackFactory(protocol.UnregistrationRequest.type)); + // See https://github.com/Microsoft/vscode-languageserver-node/issues/199 + connection.onRequest("client/unregisterFeature", _callbackFactory(protocol.UnregistrationRequest.type)); + connection.onRequest(protocol.WorkspaceFoldersRequest.type, _callbackFactory(protocol.WorkspaceFoldersRequest.type)); + connection.onRequest(function (type, params) { + var _params = { + type: type, + params: params + }; + return handler(_params); + }); +} + +exports.initialize = initialize; +exports.initialized = initialized; +exports.shutdown = shutdown; +exports.exit = exit; +exports.onCustom = onCustom; +exports.sendCustomRequest = sendCustomRequest; +exports.sendCustomNotification = sendCustomNotification; +exports.processRequest = processRequest; +exports.processNotification = processNotification; +exports.attachOnNotificationHandlers = attachOnNotificationHandlers; +exports.attachOnRequestHandlers = attachOnRequestHandlers; diff --git a/src/languageTools/LanguageClient/ServerUtils.js b/src/languageTools/LanguageClient/ServerUtils.js new file mode 100644 index 00000000000..3e865a5c1a7 --- /dev/null +++ b/src/languageTools/LanguageClient/ServerUtils.js @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports, process, Promise, __dirname, global*/ +/*eslint no-console: 0*/ +/*eslint no-fallthrough: 0*/ +/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ +(function () { + "use strict"; + + var protocol = require("vscode-languageserver-protocol"), + cp = require("child_process"), + fs = require("fs"); + + var CommunicationTypes = { + NodeIPC: { + type: "ipc", + flag: "--node-ipc" + }, + StandardIO: { + type: "stdio", + flag: "--stdio" + }, + Pipe: { + type: "pipe", + flag: "--pipe" + }, + Socket: { + type: "socket", + flag: "--socket" + } + }, + CLIENT_PROCESS_ID_FLAG = "--clientProcessId"; + + function addCommunicationArgs(communication, processArgs, isRuntime) { + switch (communication) { + case CommunicationTypes.NodeIPC.type: + { + if (isRuntime) { + processArgs.options.stdio = [null, null, null, 'ipc']; + processArgs.args.push(CommunicationTypes.NodeIPC.flag); + } else { + processArgs.args.push(CommunicationTypes.NodeIPC.flag); + } + break; + } + case CommunicationTypes.StandardIO.type: + { + processArgs.args.push(CommunicationTypes.StandardIO.flag); + break; + } + case CommunicationTypes.Pipe.type: + { + var pipeName = protocol.generateRandomPipeName(), + pipeflag = CommunicationTypes.Pipe.flag + "=" + pipeName.toString(); + + processArgs.args.push(pipeflag); + processArgs.pipeName = pipeName; + break; + } + default: + { + if (communication && communication.type === CommunicationTypes.Socket.type) { + var socketFlag = CommunicationTypes.Socket.flag + "=" + communication.port.toString(); + processArgs.args.push(socketFlag); + } + } + } + + var clientProcessIdFlag = CLIENT_PROCESS_ID_FLAG + "=" + process.pid.toString(); + processArgs.args.push(clientProcessIdFlag); + } + + function _getEnvironment(env) { + if (!env) { + return process.env; + } + + //Combine env vars + var result = Object.assign({}, process.env, env); + return result; + } + + function _createReaderAndWriteByCommunicationType(resp, type) { + var retval = null; + + switch (type) { + case CommunicationTypes.NodeIPC.type: + { + if (resp.process) { + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + + resp.process.stdout.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.info('[Server logs @ stdout] "%s"', String(data)); + } + }); + + retval = { + reader: new protocol.IPCMessageReader(resp.process), + writer: new protocol.IPCMessageWriter(resp.process) + }; + } + break; + } + case CommunicationTypes.StandardIO.type: + { + if (resp.process) { + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + + retval = { + reader: new protocol.StreamMessageReader(resp.process.stdout), + writer: new protocol.StreamMessageWriter(resp.process.stdin) + }; + } + break; + } + case CommunicationTypes.Pipe.type: + case CommunicationTypes.Socket.type: + { + if (resp.reader && resp.writer && resp.process) { + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + + resp.process.stdout.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.info('[Server logs @ stdout] "%s"', String(data)); + } + }); + + retval = { + reader: resp.reader, + writer: resp.writer + }; + } + } + } + + return retval; + } + + function _createReaderAndWriter(resp) { + var retval = null; + + if (!resp) { + return retval; + } + + if (resp.reader && resp.writer) { + retval = { + reader: resp.reader, + writer: resp.writer + }; + } else if (resp.process) { + retval = { + reader: new protocol.StreamMessageReader(resp.process.stdout), + writer: new protocol.StreamMessageWriter(resp.process.stdin) + }; + + resp.process.stderr.on('data', function (data) { + if (global.LanguageClientInfo.preferences.showServerLogsInConsole) { + console.error('[Server logs @ stderr] "%s"', String(data)); + } + }); + } + + return retval; + } + + function _isServerProcessValid(serverProcess) { + if (!serverProcess || !serverProcess.pid) { + return false; + } + + return true; + } + + function _startServerAndGetTransports(communication, processArgs, isRuntime) { + return new Promise(function (resolve, reject) { + var serverProcess = null, + result = null, + protocolTransport = null, + type = typeof communication === "object" ? communication.type : communication; + + var processFunc = isRuntime ? cp.spawn : cp.fork; + + switch (type) { + case CommunicationTypes.NodeIPC.type: + case CommunicationTypes.StandardIO.type: + { + serverProcess = processFunc(processArgs.primaryArg, processArgs.args, processArgs.options); + if (_isServerProcessValid(serverProcess)) { + result = _createReaderAndWriteByCommunicationType({ + process: serverProcess + }, type); + + resolve(result); + } else { + reject(null); + } + break; + } + case CommunicationTypes.Pipe.type: + { + protocolTransport = protocol.createClientPipeTransport(processArgs.pipeName); + } + case CommunicationTypes.Socket.type: + { + if (communication && communication.type === CommunicationTypes.Socket.type) { + protocolTransport = protocol.createClientSocketTransport(communication.port); + } + + if (!protocolTransport) { + reject("Invalid Communications Object. Can't create connection with server"); + return; + } + + protocolTransport.then(function (transportObj) { + serverProcess = processFunc(processArgs.primaryArg, processArgs.args, processArgs.options); + if (_isServerProcessValid(serverProcess)) { + transportObj.onConnected().then(function (protocolObj) { + result = _createReaderAndWriteByCommunicationType({ + process: serverProcess, + reader: protocolObj[0], + writer: protocolObj[1] + }, type); + + resolve(result); + }).catch(reject); + } + }).catch(reject); + } + } + }); + } + + function _handleOtherRuntime(serverOptions) { + function _getArguments(sOptions) { + var args = []; + + if (sOptions.options && sOptions.options.execArgv) { + args = args.concat(sOptions.options.execArgv); + } + + args.push(sOptions.module); + if (sOptions.args) { + args = args.concat(sOptions.args); + } + + return args; + } + + function _getOptions(sOptions) { + var cwd = undefined, + env = undefined; + + if (sOptions.options) { + if (sOptions.options.cwd) { + try { + if (fs.lstatSync(sOptions.options.cwd).isDirectory(sOptions.options.cwd)) { + cwd = sOptions.options.cwd; + } + } catch (e) {} + } + + cwd = cwd || __dirname; + if (sOptions.options.env) { + env = sOptions.options.env; + } + } + + var options = { + cwd: cwd, + env: _getEnvironment(env) + }; + + return options; + } + + var communication = serverOptions.communication || CommunicationTypes.StandardIO.type, + args = _getArguments(serverOptions), + options = _getOptions(serverOptions), + processArgs = { + args: args, + options: options, + primaryArg: serverOptions.runtime + }; + + addCommunicationArgs(communication, processArgs, true); + return _startServerAndGetTransports(communication, processArgs, true); + } + + function _handleNodeRuntime(serverOptions) { + function _getArguments(sOptions) { + var args = []; + + if (sOptions.args) { + args = args.concat(sOptions.args); + } + + return args; + } + + function _getOptions(sOptions) { + var cwd = undefined; + + if (sOptions.options) { + if (sOptions.options.cwd) { + try { + if (fs.lstatSync(sOptions.options.cwd).isDirectory(sOptions.options.cwd)) { + cwd = sOptions.options.cwd; + } + } catch (e) {} + } + cwd = cwd || __dirname; + } + + var options = Object.assign({}, sOptions.options); + options.cwd = cwd, + options.execArgv = options.execArgv || []; + options.silent = true; + + return options; + } + + var communication = serverOptions.communication || CommunicationTypes.StandardIO.type, + args = _getArguments(serverOptions), + options = _getOptions(serverOptions), + processArgs = { + args: args, + options: options, + primaryArg: serverOptions.module + }; + + addCommunicationArgs(communication, processArgs, false); + return _startServerAndGetTransports(communication, processArgs, false); + } + + + function _handleServerFunction(func) { + return func().then(function (resp) { + var result = _createReaderAndWriter(resp); + + return result; + }); + } + + function _handleModules(serverOptions) { + if (serverOptions.runtime) { + return _handleOtherRuntime(serverOptions); + } + return _handleNodeRuntime(serverOptions); + + } + + function _handleExecutable(serverOptions) { + return new Promise(function (resolve, reject) { + var command = serverOptions.command, + args = serverOptions.args, + options = Object.assign({}, serverOptions.options); + + var serverProcess = cp.spawn(command, args, options); + if (!serverProcess || !serverProcess.pid) { + reject("Failed to launch server using command :", command); + } + + var result = _createReaderAndWriter({ + process: serverProcess, + detached: !!options.detached + }); + + if (result) { + resolve(result); + } else { + reject(result); + } + }); + } + + function startServerAndGetConnectionArgs(serverOptions) { + if (typeof serverOptions === "function") { + return _handleServerFunction(serverOptions); + } else if (typeof serverOptions === "object") { + if (serverOptions.module) { + return _handleModules(serverOptions); + } else if (serverOptions.command) { + return _handleExecutable(serverOptions); + } + } + + return Promise.reject(null); + } + + + exports.startServerAndGetConnectionArgs = startServerAndGetConnectionArgs; +}()); diff --git a/src/languageTools/LanguageClient/Utils.js b/src/languageTools/LanguageClient/Utils.js new file mode 100644 index 00000000000..28df1c0b682 --- /dev/null +++ b/src/languageTools/LanguageClient/Utils.js @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint-env es6, node*/ +"use strict"; + +var nodeURL = require("url"), + path = require("path"); + +function pathToUri(filePath) { + var newPath = convertWinToPosixPath(filePath); + if (newPath[0] !== '/') { + newPath = `/${newPath}`; + } + return encodeURI(`file://${newPath}`).replace(/[?#]/g, encodeURIComponent); +} + +function uriToPath(uri) { + var url = nodeURL.URL.parse(uri); + if (url.protocol !== 'file:' || url.path === undefined) { + return uri; + } + + let filePath = decodeURIComponent(url.path); + if (process.platform === 'win32') { + if (filePath[0] === '/') { + filePath = filePath.substr(1); + } + return filePath; + } + return filePath; +} + +function convertPosixToWinPath(filePath) { + return filePath.replace(/\//g, '\\'); +} + +function convertWinToPosixPath(filePath) { + return filePath.replace(/\\/g, '/'); +} + +function convertToLSPPosition(pos) { + return { + line: pos.line, + character: pos.ch + }; +} + +function convertToWorkspaceFolders(paths) { + var workspaceFolders = paths.map(function (folderPath) { + var uri = pathToUri(folderPath), + name = path.basename(folderPath); + + return { + uri: uri, + name: name + }; + }); + + return workspaceFolders; +} + +exports.uriToPath = uriToPath; +exports.pathToUri = pathToUri; +exports.convertPosixToWinPath = convertPosixToWinPath; +exports.convertWinToPosixPath = convertWinToPosixPath; +exports.convertToLSPPosition = convertToLSPPosition; +exports.convertToWorkspaceFolders = convertToWorkspaceFolders; diff --git a/src/languageTools/LanguageClient/package.json b/src/languageTools/LanguageClient/package.json new file mode 100644 index 00000000000..2fdc6b6094a --- /dev/null +++ b/src/languageTools/LanguageClient/package.json @@ -0,0 +1,19 @@ +{ + "name": "brackets-language-client", + "version": "1.0.0", + "description": "Brackets language client interface for Language Server Protocol", + "main": "LanguageClient.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "LSP", + "LanguageClient", + "Brackets" + ], + "author": "Adobe", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "^3.14.1" + } +} diff --git a/src/languageTools/LanguageClientWrapper.js b/src/languageTools/LanguageClientWrapper.js new file mode 100644 index 00000000000..abd0c9b41f5 --- /dev/null +++ b/src/languageTools/LanguageClientWrapper.js @@ -0,0 +1,627 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-console: 0*/ +/*eslint indent: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var ToolingInfo = JSON.parse(require("text!languageTools/ToolingInfo.json")), + MESSAGE_FORMAT = { + BRACKETS: "brackets", + LSP: "lsp" + }; + + function _addTypeInformation(type, params) { + return { + type: type, + params: params + }; + } + + function hasValidProp(obj, prop) { + return (obj && obj[prop] !== undefined && obj[prop] !== null); + } + + function hasValidProps(obj, props) { + var retval = !!obj, + len = props.length, + i; + + for (i = 0; retval && (i < len); i++) { + retval = (retval && obj[props[i]] !== undefined && obj[props[i]] !== null); + } + + return retval; + } + /* + RequestParams creator - sendNotifications/request + */ + function validateRequestParams(type, params) { + var validatedParams = null; + + params = params || {}; + + //Don't validate if the formatting is done by the caller + if (params.format === MESSAGE_FORMAT.LSP) { + return params; + } + + switch (type) { + case ToolingInfo.LANGUAGE_SERVICE.START: + { + if (hasValidProp(params, "rootPaths") || hasValidProp(params, "rootPath")) { + validatedParams = params; + validatedParams.capabilities = validatedParams.capabilities || false; + } + break; + } + case ToolingInfo.FEATURES.CODE_HINTS: + case ToolingInfo.FEATURES.PARAMETER_HINTS: + case ToolingInfo.FEATURES.JUMP_TO_DECLARATION: + case ToolingInfo.FEATURES.JUMP_TO_DEFINITION: + case ToolingInfo.FEATURES.JUMP_TO_IMPL: + { + if (hasValidProps(params, ["filePath", "cursorPos"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.FEATURES.CODE_HINT_INFO: + { + validatedParams = params; + break; + } + case ToolingInfo.FEATURES.FIND_REFERENCES: + { + if (hasValidProps(params, ["filePath", "cursorPos"])) { + validatedParams = params; + validatedParams.includeDeclaration = validatedParams.includeDeclaration || false; + } + break; + } + case ToolingInfo.FEATURES.DOCUMENT_SYMBOLS: + { + if (hasValidProp(params, "filePath")) { + validatedParams = params; + } + break; + } + case ToolingInfo.FEATURES.PROJECT_SYMBOLS: + { + if (params && params.query && typeof params.query === "string") { + validatedParams = params; + } + break; + } + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST: + { + validatedParams = params; + } + } + + return validatedParams; + } + + /* + ReponseParams transformer - used by OnNotifications + */ + function validateNotificationParams(type, params) { + var validatedParams = null; + + params = params || {}; + + //Don't validate if the formatting is done by the caller + if (params.format === MESSAGE_FORMAT.LSP) { + return params; + } + + switch (type) { + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED: + { + if (hasValidProps(params, ["filePath", "fileContent", "languageId"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED: + { + if (hasValidProps(params, ["filePath", "fileContent"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED: + { + if (hasValidProp(params, "filePath")) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CLOSED: + { + if (hasValidProp(params, "filePath")) { + validatedParams = params; + } + break; + } + case ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED: + { + if (hasValidProps(params, ["foldersAdded", "foldersRemoved"])) { + validatedParams = params; + } + break; + } + case ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION: + { + validatedParams = params; + } + } + + return validatedParams; + } + + function validateHandler(handler) { + var retval = false; + + if (handler && typeof handler === "function") { + retval = true; + } else { + console.warn("Handler validation failed. Handler should be of type 'function'. Provided handler is of type :", typeof handler); + } + + return retval; + } + + function LanguageClientWrapper(name, path, domainInterface, languages) { + this._name = name; + this._path = path; + this._domainInterface = domainInterface; + this._languages = languages || []; + this._startClient = null; + this._stopClient = null; + this._notifyClient = null; + this._requestClient = null; + this._onRequestHandler = {}; + this._onNotificationHandlers = {}; + this._dynamicCapabilities = {}; + this._serverCapabilities = {}; + + //Initialize with keys for brackets events we want to tap into. + this._onEventHandlers = { + "activeEditorChange": [], + "projectOpen": [], + "beforeProjectClose": [], + "dirtyFlagChange": [], + "documentChange": [], + "fileNameChange": [], + "beforeAppClose": [] + }; + + this._init(); + } + + LanguageClientWrapper.prototype._init = function () { + this._domainInterface.registerMethods([ + { + methodName: ToolingInfo.LANGUAGE_SERVICE.REQUEST, + methodHandle: this._onRequestDelegator.bind(this) + }, + { + methodName: ToolingInfo.LANGUAGE_SERVICE.NOTIFY, + methodHandle: this._onNotificationDelegator.bind(this) + } + ]); + + //create function interfaces + this._startClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.START, true); + this._stopClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.STOP, true); + this._notifyClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.NOTIFY); + this._requestClient = this._domainInterface.createInterface(ToolingInfo.LANGUAGE_SERVICE.REQUEST, true); + }; + + LanguageClientWrapper.prototype._onRequestDelegator = function (params) { + if (!params || !params.type) { + console.log("Invalid server request"); + return $.Deferred().reject(); + } + + var requestHandler = this._onRequestHandler[params.type]; + if (params.type === ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST) { + return this._registrationShim(params.params, requestHandler); + } + + if (params.type === ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST) { + return this._unregistrationShim(params.params, requestHandler); + } + + if (validateHandler(requestHandler)) { + return requestHandler.call(null, params.params); + } + console.log("No handler provided for server request type : ", params.type); + return $.Deferred().reject(); + + }; + + LanguageClientWrapper.prototype._onNotificationDelegator = function (params) { + if (!params || !params.type) { + console.log("Invalid server notification"); + return; + } + + var notificationHandlers = this._onNotificationHandlers[params.type]; + if (notificationHandlers && Array.isArray(notificationHandlers) && notificationHandlers.length) { + notificationHandlers.forEach(function (handler) { + if (validateHandler(handler)) { + handler.call(null, params.params); + } + }); + } else { + console.log("No handlers provided for server notification type : ", params.type); + } + }; + + LanguageClientWrapper.prototype._request = function (type, params) { + params = validateRequestParams(type, params); + if (params) { + params = _addTypeInformation(type, params); + return this._requestClient(params); + } + + console.log("Invalid Parameters provided for request type : ", type); + return $.Deferred().reject(); + }; + + LanguageClientWrapper.prototype._notify = function (type, params) { + params = validateNotificationParams(type, params); + if (params) { + params = _addTypeInformation(type, params); + this._notifyClient(params); + } else { + console.log("Invalid Parameters provided for notification type : ", type); + } + }; + + LanguageClientWrapper.prototype._addOnRequestHandler = function (type, handler) { + if (validateHandler(handler)) { + this._onRequestHandler[type] = handler; + } + }; + + LanguageClientWrapper.prototype._addOnNotificationHandler = function (type, handler) { + if (validateHandler(handler)) { + if (!this._onNotificationHandlers[type]) { + this._onNotificationHandlers[type] = []; + } + + this._onNotificationHandlers[type].push(handler); + } + }; + + /** + Requests + */ + //start + LanguageClientWrapper.prototype.start = function (params) { + params = validateRequestParams(ToolingInfo.LANGUAGE_SERVICE.START, params); + if (params) { + var self = this; + return this._startClient(params) + .then(function (result) { + self.setServerCapabilities(result.capabilities); + return $.Deferred().resolve(result); + }, function (err) { + return $.Deferred().reject(err); + }); + } + + console.log("Invalid Parameters provided for request type : start"); + return $.Deferred().reject(); + }; + + //shutdown + LanguageClientWrapper.prototype.stop = function () { + return this._stopClient(); + }; + + //restart + LanguageClientWrapper.prototype.restart = function (params) { + var self = this; + return this.stop().then(function () { + return self.start(params); + }); + }; + + /** + textDocument requests + */ + //completion + LanguageClientWrapper.prototype.requestHints = function (params) { + return this._request(ToolingInfo.FEATURES.CODE_HINTS, params); + }; + + //completionItemResolve + LanguageClientWrapper.prototype.getAdditionalInfoForHint = function (params) { + return this._request(ToolingInfo.FEATURES.CODE_HINT_INFO, params); + }; + + //signatureHelp + LanguageClientWrapper.prototype.requestParameterHints = function (params) { + return this._request(ToolingInfo.FEATURES.PARAMETER_HINTS, params); + }; + + //gotoDefinition + LanguageClientWrapper.prototype.gotoDefinition = function (params) { + return this._request(ToolingInfo.FEATURES.JUMP_TO_DEFINITION, params); + }; + + //gotoDeclaration + LanguageClientWrapper.prototype.gotoDeclaration = function (params) { + return this._request(ToolingInfo.FEATURES.JUMP_TO_DECLARATION, params); + }; + + //gotoImplementation + LanguageClientWrapper.prototype.gotoImplementation = function (params) { + return this._request(ToolingInfo.FEATURES.JUMP_TO_IMPL, params); + }; + + //findReferences + LanguageClientWrapper.prototype.findReferences = function (params) { + return this._request(ToolingInfo.FEATURES.FIND_REFERENCES, params); + }; + + //documentSymbol + LanguageClientWrapper.prototype.requestSymbolsForDocument = function (params) { + return this._request(ToolingInfo.FEATURES.DOCUMENT_SYMBOLS, params); + }; + + /** + workspace requests + */ + //workspaceSymbol + LanguageClientWrapper.prototype.requestSymbolsForWorkspace = function (params) { + return this._request(ToolingInfo.FEATURES.PROJECT_SYMBOLS, params); + }; + + //These will mostly be callbacks/[done-fail](promises) + /** + Window OnNotifications + */ + //showMessage + LanguageClientWrapper.prototype.addOnShowMessage = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.SHOW_MESSAGE, handler); + }; + + //logMessage + LanguageClientWrapper.prototype.addOnLogMessage = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.LOG_MESSAGE, handler); + }; + + /** + healthData/logging OnNotifications + */ + //telemetry + LanguageClientWrapper.prototype.addOnTelemetryEvent = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.TELEMETRY, handler); + }; + + /** + textDocument OnNotifications + */ + //onPublishDiagnostics + LanguageClientWrapper.prototype.addOnCodeInspection = function (handler) { + this._addOnNotificationHandler(ToolingInfo.SERVICE_NOTIFICATIONS.DIAGNOSTICS, handler); + }; + + /** + Window OnRequest + */ + + //showMessageRequest - handler must return promise + LanguageClientWrapper.prototype.onShowMessageWithRequest = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.SHOW_SELECT_MESSAGE, handler); + }; + + LanguageClientWrapper.prototype.onProjectFoldersRequest = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.PROJECT_FOLDERS_REQUEST, handler); + }; + + LanguageClientWrapper.prototype._registrationShim = function (params, handler) { + var self = this; + + var registrations = params.registrations; + registrations.forEach(function (registration) { + var id = registration.id; + self._dynamicCapabilities[id] = registration; + }); + return validateHandler(handler) ? handler(params) : $.Deferred().resolve(); + }; + + LanguageClientWrapper.prototype.onDynamicCapabilityRegistration = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.REGISTRATION_REQUEST, handler); + }; + + LanguageClientWrapper.prototype._unregistrationShim = function (params, handler) { + var self = this; + + var unregistrations = params.unregistrations; + unregistrations.forEach(function (unregistration) { + var id = unregistration.id; + delete self._dynamicCapabilities[id]; + }); + return validateHandler(handler) ? handler(params) : $.Deferred().resolve(); + }; + + LanguageClientWrapper.prototype.onDynamicCapabilityUnregistration = function (handler) { + this._addOnRequestHandler(ToolingInfo.SERVICE_REQUESTS.UNREGISTRATION_REQUEST, handler); + }; + + /* + Unimplemented OnNotifications + workspace + applyEdit (codeAction, codeLens) + */ + + /** + SendNotifications + */ + + /** + workspace SendNotifications + */ + //didChangeProjectRoots + LanguageClientWrapper.prototype.notifyProjectRootsChanged = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED, params); + }; + + /** + textDocument SendNotifications + */ + //didOpenTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentOpened = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED, params); + }; + + //didCloseTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentClosed = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CLOSED, params); + }; + + //didChangeTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentChanged = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED, params); + }; + + //didSaveTextDocument + LanguageClientWrapper.prototype.notifyTextDocumentSave = function (params) { + this._notify(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED, params); + }; + + /** + Custom messages + */ + + //customNotification + LanguageClientWrapper.prototype.sendCustomNotification = function (params) { + this._notify(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION, params); + }; + + LanguageClientWrapper.prototype.onCustomNotification = function (type, handler) { + this._addOnNotificationHandler(type, handler); + }; + + //customRequest + LanguageClientWrapper.prototype.sendCustomRequest = function (params) { + return this._request(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST, params); + }; + + LanguageClientWrapper.prototype.onCustomRequest = function (type, handler) { + this._addOnRequestHandler(type, handler); + }; + + //Handling Brackets Events + LanguageClientWrapper.prototype.addOnEditorChangeHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["activeEditorChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnProjectOpenHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["projectOpen"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addBeforeProjectCloseHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["beforeProjectClose"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnDocumentDirtyFlagChangeHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["dirtyFlagChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnDocumentChangeHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["documentChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnFileRenameHandler = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["fileNameChange"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addBeforeAppClose = function (handler) { + if (validateHandler(handler)) { + this._onEventHandlers["beforeAppClose"].push(handler); + } + }; + + LanguageClientWrapper.prototype.addOnCustomEventHandler = function (eventName, handler) { + if (validateHandler(handler)) { + if (!this._onEventHandlers[eventName]) { + this._onEventHandlers[eventName] = []; + } + this._onEventHandlers[eventName].push(handler); + } + }; + + LanguageClientWrapper.prototype.triggerEvent = function (event) { + var eventName = event.type, + eventArgs = arguments; + + if (this._onEventHandlers[eventName] && Array.isArray(this._onEventHandlers[eventName])) { + var handlers = this._onEventHandlers[eventName]; + + handlers.forEach(function (handler) { + if (validateHandler(handler)) { + handler.apply(null, eventArgs); + } + }); + } + }; + + LanguageClientWrapper.prototype.getDynamicCapabilities = function () { + return this._dynamicCapabilities; + }; + + LanguageClientWrapper.prototype.getServerCapabilities = function () { + return this._serverCapabilities; + }; + + LanguageClientWrapper.prototype.setServerCapabilities = function (serverCapabilities) { + this._serverCapabilities = serverCapabilities; + }; + + exports.LanguageClientWrapper = LanguageClientWrapper; + + //For unit testting + exports.validateRequestParams = validateRequestParams; + exports.validateNotificationParams = validateNotificationParams; +}); diff --git a/src/languageTools/LanguageTools.js b/src/languageTools/LanguageTools.js new file mode 100644 index 00000000000..08d2bca6b71 --- /dev/null +++ b/src/languageTools/LanguageTools.js @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*eslint no-console: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint-env es6*/ +define(function (require, exports, module) { + "use strict"; + + var ClientLoader = require("languageTools/ClientLoader"), + EditorManager = require("editor/EditorManager"), + ProjectManager = require("project/ProjectManager"), + DocumentManager = require("document/DocumentManager"), + DocumentModule = require("document/Document"), + PreferencesManager = require("preferences/PreferencesManager"), + Strings = require("strings"), + LanguageClientWrapper = require("languageTools/LanguageClientWrapper").LanguageClientWrapper; + + var languageClients = new Map(), + languageToolsPrefs = { + showServerLogsInConsole: false + }, + BRACKETS_EVENTS_NAMES = { + EDITOR_CHANGE_EVENT: "activeEditorChange", + PROJECT_OPEN_EVENT: "projectOpen", + PROJECT_CLOSE_EVENT: "beforeProjectClose", + DOCUMENT_DIRTY_EVENT: "dirtyFlagChange", + DOCUMENT_CHANGE_EVENT: "documentChange", + FILE_RENAME_EVENT: "fileNameChange", + BEFORE_APP_CLOSE: "beforeAppClose" + }; + + PreferencesManager.definePreference("languageTools", "object", languageToolsPrefs, { + description: Strings.LANGUAGE_TOOLS_PREFERENCES + }); + + PreferencesManager.on("change", "languageTools", function () { + languageToolsPrefs = PreferencesManager.get("languageTools"); + + ClientLoader.syncPrefsWithDomain(languageToolsPrefs); + }); + + function registerLanguageClient(clientName, languageClient) { + languageClients.set(clientName, languageClient); + } + + function _withNamespace(event) { + return event.split(" ") + .filter((value) => !!value) + .map((value) => value + ".language-tools") + .join(" "); + } + + function _eventHandler() { + var eventArgs = arguments; + //Broadcast event to all clients + languageClients.forEach(function (client) { + client.triggerEvent.apply(client, eventArgs); + }); + } + + function _attachEventHandlers() { + //Attach standard listeners + EditorManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.EDITOR_CHANGE_EVENT), _eventHandler); //(event, current, previous) + ProjectManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.PROJECT_OPEN_EVENT), _eventHandler); //(event, directory) + ProjectManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.PROJECT_CLOSE_EVENT), _eventHandler); //(event, directory) + DocumentManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.DOCUMENT_DIRTY_EVENT), _eventHandler); //(event, document) + DocumentModule.on(_withNamespace(BRACKETS_EVENTS_NAMES.DOCUMENT_CHANGE_EVENT), _eventHandler); //(event, document, changeList) + DocumentManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.FILE_RENAME_EVENT), _eventHandler); //(event, oldName, newName) + ProjectManager.on(_withNamespace(BRACKETS_EVENTS_NAMES.BEFORE_APP_CLOSE), _eventHandler); //(event, oldName, newName) + } + + _attachEventHandlers(); + + function listenToCustomEvent(eventModule, eventName) { + eventModule.on(_withNamespace(eventName), _eventHandler); + } + + function initiateToolingService(clientName, clientFilePath, languages) { + var result = $.Deferred(); + + ClientLoader.initiateLanguageClient(clientName, clientFilePath) + .done(function (languageClientInfo) { + var languageClientName = languageClientInfo.name, + languageClientInterface = languageClientInfo.interface, + languageClient = new LanguageClientWrapper(languageClientName, clientFilePath, languageClientInterface, languages); + + registerLanguageClient(languageClientName, languageClient); + + result.resolve(languageClient); + }) + .fail(result.reject); + + return result; + } + + exports.initiateToolingService = initiateToolingService; + exports.listenToCustomEvent = listenToCustomEvent; +}); diff --git a/src/languageTools/PathConverters.js b/src/languageTools/PathConverters.js new file mode 100644 index 00000000000..c4bbc7de898 --- /dev/null +++ b/src/languageTools/PathConverters.js @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint-disable indent */ +define(function (require, exports, module) { + "use strict"; + + var PathUtils = require("thirdparty/path-utils/path-utils"), + FileUtils = require("file/FileUtils"); + + function uriToPath(uri) { + var url = PathUtils.parseUrl(uri); + if (url.protocol !== 'file:' || url.pathname === undefined) { + return uri; + } + + let filePath = decodeURIComponent(url.pathname); + if (brackets.platform === 'win') { + if (filePath[0] === '/') { + filePath = filePath.substr(1); + } + return filePath; + } + return filePath; + } + + function pathToUri(filePath) { + var newPath = convertWinToPosixPath(filePath); + if (newPath[0] !== '/') { + newPath = `/${newPath}`; + } + return encodeURI(`file://${newPath}`).replace(/[?#]/g, encodeURIComponent); + } + + function convertToWorkspaceFolders(paths) { + var workspaceFolders = paths.map(function (folderPath) { + var uri = pathToUri(folderPath), + name = FileUtils.getBasename(folderPath); + + return { + uri: uri, + name: name + }; + }); + + return workspaceFolders; + } + + function convertPosixToWinPath(path) { + return path.replace(/\//g, '\\'); + } + + function convertWinToPosixPath(path) { + return path.replace(/\\/g, '/'); + } + + exports.uriToPath = uriToPath; + exports.pathToUri = pathToUri; + exports.convertPosixToWinPath = convertPosixToWinPath; + exports.convertPosixToWinPath = convertPosixToWinPath; + exports.convertToWorkspaceFolders = convertToWorkspaceFolders; +}); diff --git a/src/languageTools/ToolingInfo.json b/src/languageTools/ToolingInfo.json new file mode 100644 index 00000000000..d7457ec6a9e --- /dev/null +++ b/src/languageTools/ToolingInfo.json @@ -0,0 +1,41 @@ +{ + "LANGUAGE_SERVICE": { + "START": "start", + "STOP": "stop", + "REQUEST": "request", + "NOTIFY": "notify", + "CANCEL_REQUEST": "cancelRequest", + "CUSTOM_REQUEST": "customRequest", + "CUSTOM_NOTIFICATION": "customNotification" + }, + "SERVICE_NOTIFICATIONS": { + "SHOW_MESSAGE": "showMessage", + "LOG_MESSAGE": "logMessage", + "TELEMETRY": "telemetry", + "DIAGNOSTICS": "diagnostics" + }, + "SERVICE_REQUESTS": { + "SHOW_SELECT_MESSAGE": "showSelectMessage", + "REGISTRATION_REQUEST": "registerDynamicCapability", + "UNREGISTRATION_REQUEST": "unregisterDynamicCapability", + "PROJECT_FOLDERS_REQUEST": "projectFoldersRequest" + }, + "SYNCHRONIZE_EVENTS": { + "DOCUMENT_OPENED": "didOpen", + "DOCUMENT_CHANGED": "didChange", + "DOCUMENT_SAVED": "didSave", + "DOCUMENT_CLOSED": "didClose", + "PROJECT_FOLDERS_CHANGED": "projectRootsChanged" + }, + "FEATURES": { + "CODE_HINTS": "codehints", + "CODE_HINT_INFO": "hintInfo", + "PARAMETER_HINTS": "parameterHints", + "JUMP_TO_DECLARATION": "declaration", + "JUMP_TO_DEFINITION": "definition", + "JUMP_TO_IMPL": "implementation", + "FIND_REFERENCES": "references", + "DOCUMENT_SYMBOLS": "documentSymbols", + "PROJECT_SYMBOLS": "projectSymbols" + } +} diff --git a/src/languageTools/node/RegisterLanguageClientInfo.js b/src/languageTools/node/RegisterLanguageClientInfo.js new file mode 100644 index 00000000000..614be647561 --- /dev/null +++ b/src/languageTools/node/RegisterLanguageClientInfo.js @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/*global exports*/ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +"use strict"; + +var domainName = "LanguageClientInfo", + LANGUAGE_CLIENT_RELATIVE_PATH_ARRAY = ["languageTools", "LanguageClient", "LanguageClient"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\", + CompletionItemKind = { + Text: 1, + Method: 2, + Function: 3, + Constructor: 4, + Field: 5, + Variable: 6, + Class: 7, + Interface: 8, + Module: 9, + Property: 10, + Unit: 11, + Value: 12, + Enum: 13, + Keyword: 14, + Snippet: 15, + Color: 16, + File: 17, + Reference: 18, + Folder: 19, + EnumMember: 20, + Constant: 21, + Struct: 22, + Event: 23, + Operator: 24, + TypeParameter: 25 + }, + SymbolKind = { + File: 1, + Module: 2, + Namespace: 3, + Package: 4, + Class: 5, + Method: 6, + Property: 7, + Field: 8, + Constructor: 9, + Enum: 10, + Interface: 11, + Function: 12, + Variable: 13, + Constant: 14, + String: 15, + Number: 16, + Boolean: 17, + Array: 18, + Object: 19, + Key: 20, + Null: 21, + EnumMember: 22, + Struct: 23, + Event: 24, + Operator: 25, + TypeParameter: 26 + }, + defaultBracketsCapabilities = { + //brackets default capabilties + "workspace": { + "workspaceFolders": true, + "symbol": { + "dynamicRegistration": false, + "symbolKind": [ + SymbolKind.File, + SymbolKind.Module, + SymbolKind.Namespace, + SymbolKind.Package, + SymbolKind.Class, + SymbolKind.Method, + SymbolKind.Property, + SymbolKind.Field, + SymbolKind.Constructor, + SymbolKind.Enum, + SymbolKind.Interface, + SymbolKind.Function, + SymbolKind.Variable, + SymbolKind.Constant, + SymbolKind.String, + SymbolKind.Number, + SymbolKind.Boolean, + SymbolKind.Array, + SymbolKind.Object, + SymbolKind.Key, + SymbolKind.Null, + SymbolKind.EnumMember, + SymbolKind.Struct, + SymbolKind.Event, + SymbolKind.Operator, + SymbolKind.TypeParameter + ] + } + }, + "textDocument": { + "synchronization": { + "didSave": true + }, + "completion": { + "dynamicRegistration": false, + "completionItem": { + "deprecatedSupport": true, + "documentationFormat": ["plaintext"], + "preselectSupport": true + }, + "completionItemKind": { + "valueSet": [ + CompletionItemKind.Text, + CompletionItemKind.Method, + CompletionItemKind.Function, + CompletionItemKind.Constructor, + CompletionItemKind.Field, + CompletionItemKind.Variable, + CompletionItemKind.Class, + CompletionItemKind.Interface, + CompletionItemKind.Module, + CompletionItemKind.Property, + CompletionItemKind.Unit, + CompletionItemKind.Value, + CompletionItemKind.Enum, + CompletionItemKind.Keyword, + CompletionItemKind.Snippet, + CompletionItemKind.Color, + CompletionItemKind.File, + CompletionItemKind.Reference, + CompletionItemKind.Folder, + CompletionItemKind.EnumMember, + CompletionItemKind.Constant, + CompletionItemKind.Struct, + CompletionItemKind.Event, + CompletionItemKind.Operator, + CompletionItemKind.TypeParameter + ] + }, + "contextSupport": true + }, + "signatureHelp": { + "dynamicRegistration": false, + "signatureInformation": { + "documentationFormat": ["plaintext"] + } + }, + "references": { + "dynamicRegistration": false + }, + "documentSymbol": { + "dynamicRegistration": false, + "symbolKind": { + "valueSet": [ + SymbolKind.File, + SymbolKind.Module, + SymbolKind.Namespace, + SymbolKind.Package, + SymbolKind.Class, + SymbolKind.Method, + SymbolKind.Property, + SymbolKind.Field, + SymbolKind.Constructor, + SymbolKind.Enum, + SymbolKind.Interface, + SymbolKind.Function, + SymbolKind.Variable, + SymbolKind.Constant, + SymbolKind.String, + SymbolKind.Number, + SymbolKind.Boolean, + SymbolKind.Array, + SymbolKind.Object, + SymbolKind.Key, + SymbolKind.Null, + SymbolKind.EnumMember, + SymbolKind.Struct, + SymbolKind.Event, + SymbolKind.Operator, + SymbolKind.TypeParameter + ] + }, + "hierarchicalDocumentSymbolSupport": false + }, + "definition": { + "dynamicRegistration": false + }, + "declaration": { + "dynamicRegistration": false + }, + "typeDefinition": { + "dynamicRegistration": false + }, + "implementation": { + "dynamicRegistration": false + }, + "publishDiagnostics": { + "relatedInformation": true + } + } + }; + +function syncPreferences(prefs) { + global.LanguageClientInfo = global.LanguageClientInfo || {}; + global.LanguageClientInfo.preferences = prefs || global.LanguageClientInfo.preferences || {}; +} + +function initialize(bracketsSourcePath, toolingInfo) { + var normalizedBracketsSourcePath = bracketsSourcePath.split(BACKWARD_SLASH).join(FORWARD_SLASH), + bracketsSourcePathArray = normalizedBracketsSourcePath.split(FORWARD_SLASH), + languageClientAbsolutePath = bracketsSourcePathArray.concat(LANGUAGE_CLIENT_RELATIVE_PATH_ARRAY).join(FORWARD_SLASH); + + global.LanguageClientInfo = global.LanguageClientInfo || {}; + global.LanguageClientInfo.languageClientPath = languageClientAbsolutePath; + global.LanguageClientInfo.defaultBracketsCapabilities = defaultBracketsCapabilities; + global.LanguageClientInfo.toolingInfo = toolingInfo; + global.LanguageClientInfo.preferences = {}; +} + +function init(domainManager) { + if (!domainManager.hasDomain(domainName)) { + domainManager.registerDomain(domainName, { + major: 0, + minor: 1 + }); + } + + domainManager.registerCommand( + domainName, + "initialize", + initialize, + false, + "Initialize node environment for Language Client Module", + [ + { + name: "bracketsSourcePath", + type: "string", + description: "Absolute path to the brackets source" + }, + { + name: "toolingInfo", + type: "object", + description: "Tooling Info json to be used by Language Client" + } + ], + [] + ); + + domainManager.registerCommand( + domainName, + "syncPreferences", + syncPreferences, + false, + "Sync language tools preferences for Language Client Module", + [ + { + name: "prefs", + type: "object", + description: "Language tools preferences" + } + ], + [] + ); +} + +exports.init = init; diff --git a/src/languageTools/styles/default_provider_style.css b/src/languageTools/styles/default_provider_style.css new file mode 100644 index 00000000000..5090330b28c --- /dev/null +++ b/src/languageTools/styles/default_provider_style.css @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + + +span.brackets-hints-with-type-details { + width: 300px; + display: inline-block; +} + +.brackets-hints-type-details { + color: #a3a3a3 !important; + font-weight: 100; + font-style: italic !important; + margin-right: 5px; + float: right; +} + +.hint-description { + display: none; + color: #d4d4d4; + word-wrap: break-word; + white-space: normal; + box-sizing: border-box; +} + +.hint-doc { + display: none; + padding-right: 10px !important; + color: grey; + word-wrap: break-word; + white-space: normal; + box-sizing: border-box; + float: left; + clear: left; + max-height: 2em; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1em; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.highlight .hint-description { + display: block; + color: #6495ed !important; +} + +.highlight .hint-doc { + display: -webkit-box; +} + +.dark .brackets-hints-type-details { + color: #696969 !important; +} + +.highlight .brackets-hints-type-details { + display: none; +} + +.brackets-hints-keyword { + font-weight: 100; + font-style: italic !important; + margin-right: 5px; + float: right; + color: #6495ed !important; +} + +.brackets-hints .matched-hint { + font-weight: 500; +} + +#function-hint-container-new { + display: none; + + background: #fff; + position: absolute; + z-index: 15; + left: 400px; + top: 40px; + height: auto; + width: auto; + overflow: scroll; + + padding: 1px 6px; + text-align: center; + + border-radius: 3px; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.24); +} + +#function-hint-container-new .function-hint-content-new { + text-align: left; +} + +.brackets-hints .current-parameter { + font-weight: 500; +} + +/* Dark Styles */ + +.dark #function-hint-container-new { + background: #000; + border: 1px solid rgba(255, 255, 255, 0.15); + color: #fff; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.24); +} + +.dark .hint-doc { + color: #ccc; +} + +.dark .hint-description { + color: #ccc; +} diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 594a566764e..074c462c438 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -875,5 +875,8 @@ define({ "NUMBER_WITH_PERCENTAGE" : "{0}%", // Strings for Related Files - "CMD_FIND_RELATED_FILES" : "Find Related Files" + "CMD_FIND_RELATED_FILES" : "Find Related Files", + + //Strings for LanguageTools Preferences + LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" }); diff --git a/tasks/npm-install.js b/tasks/npm-install.js index cad79951927..76434dce5e1 100644 --- a/tasks/npm-install.js +++ b/tasks/npm-install.js @@ -42,9 +42,10 @@ module.exports = function (grunt) { temp.track(); - function runNpmInstall(where, callback) { - grunt.log.writeln("running npm install --production in " + where); - exec('npm install --production', { cwd: './' + where }, function (err, stdout, stderr) { + function runNpmInstall(where, callback, includeDevDependencies) { + var envFlag = includeDevDependencies ? "" : " --production"; + grunt.log.writeln("running npm install" + envFlag + " in " + where); + exec('npm install' + envFlag, { cwd: './' + where }, function (err, stdout, stderr) { if (err) { grunt.log.error(stderr); } else { @@ -71,7 +72,7 @@ module.exports = function (grunt) { grunt.registerTask("npm-install-src", "Install node_modules to the src folder", function () { var _done = this.async(), - dirs = ["src", "src/JSUtils", "src/JSUtils/node"], + dirs = ["src", "src/JSUtils", "src/JSUtils/node", "src/languageTools/LanguageClient"], done = _.after(dirs.length, _done); dirs.forEach(function (dir) { runNpmInstall(dir, function (err) { @@ -99,10 +100,34 @@ module.exports = function (grunt) { }); }); + grunt.registerTask("npm-install-test", "Install node_modules for tests", function () { + var _done = this.async(); + var testDirs = [ + "spec/LanguageTools-test-files" + ]; + testDirs.forEach(function (dir) { + glob("test/" + dir + "/**/package.json", function (err, files) { + if (err) { + grunt.log.error(err); + return _done(false); + } + files = files.filter(function (path) { + return path.indexOf("node_modules") === -1; + }); + var done = _.after(files.length, _done); + files.forEach(function (file) { + runNpmInstall(path.dirname(file), function (err) { + return err ? _done(false) : done(); + }, true); + }); + }); + }); + }); + grunt.registerTask( "npm-install-source", "Install node_modules for src folder and default extensions which have package.json defined", - ["npm-install-src", "copy:thirdparty", "npm-install-extensions"] + ["npm-install-src", "copy:thirdparty", "npm-install-extensions", "npm-install-test"] ); function getNodeModulePackageUrl(extensionName) { diff --git a/test/SpecRunner.js b/test/SpecRunner.js index 6b25937ed24..075c7c48013 100644 --- a/test/SpecRunner.js +++ b/test/SpecRunner.js @@ -102,6 +102,18 @@ define(function (require, exports, module) { require("thirdparty/CodeMirror/addon/mode/overlay"); require("thirdparty/CodeMirror/addon/search/searchcursor"); require("thirdparty/CodeMirror/keymap/sublime"); + + //load Language Tools Module + require("languageTools/PathConverters"); + require("languageTools/LanguageTools"); + require("languageTools/ClientLoader"); + require("languageTools/BracketsToNodeInterface"); + require("languageTools/DefaultProviders"); + require("languageTools/DefaultEventHandlers"); + + //load language features + require("features/ParameterHintsManager"); + require("features/JumpToDefManager"); var selectedSuites, params = new UrlParams(), diff --git a/test/UnitTestSuite.js b/test/UnitTestSuite.js index b32c99f5977..5c9b54d5101 100644 --- a/test/UnitTestSuite.js +++ b/test/UnitTestSuite.js @@ -61,6 +61,7 @@ define(function (require, exports, module) { require("spec/JSONUtils-test"); require("spec/KeyBindingManager-test"); require("spec/LanguageManager-test"); + require("spec/LanguageTools-test"); require("spec/LiveDevelopment-test"); require("spec/LiveDevelopmentMultiBrowser-test"); require("spec/LowLevelFileIO-test"); diff --git a/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js new file mode 100644 index 00000000000..0f42ff886bc --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/client.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + clientName = "CommunicationTestClient", + client = null, + modulePath = null, + getPort = require("get-port"), + relativeLSPathArray = ["..", "..", "server", "lsp-test-server", "main.js"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\", + defaultPort = 3000; + +function getServerOptionsForSocket() { + return new Promise(function (resolve, reject) { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + getPort({ + port: defaultPort + }) + .then(function (port) { + + var serverOptions = { + module: serverPath, + communication: { + type: "socket", + port: port + } + }; + resolve(serverOptions); + }) + .catch(reject); + + }); +} + +function getServerOptions(type) { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + serverPath = path.resolve(serverPath); + + var serverOptions = { + module: serverPath, + communication: type + }; + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + if (!params || !params.communicationType) { + return Promise.reject("Can't start server because no communication type provided"); + } + + var cType = params.communicationType, + options = { + serverOptions: getServerOptions(cType) + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function setOptionsForSocket() { + return new Promise(function (resolve, reject) { + getServerOptionsForSocket() + .then(function (serverOptions) { + var options = { + serverOptions: serverOptions + }; + client.setOptions(options); + + resolve(); + }).catch(reject); + }); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); + client.addOnRequestHandler('setOptionsForSocket', setOptionsForSocket); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js new file mode 100644 index 00000000000..52c8ce0d28c --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/main.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "CommunicationTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(retval.resolve); + + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json new file mode 100644 index 00000000000..cc25d2ac62d --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/CommunicationTestClient/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "get-port": "^4.2.0" + } +} diff --git a/test/spec/LanguageTools-test-files/clients/FeatureClient/client.js b/test/spec/LanguageTools-test-files/clients/FeatureClient/client.js new file mode 100644 index 00000000000..95050e7e0b8 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/FeatureClient/client.js @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + clientName = "FeatureClient", + client = null, + modulePath = null, + relativeLSPathArray = ["..", "..", "server", "lsp-test-server", "main.js"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\"; + +function getServerOptions() { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + serverPath = path.resolve(serverPath); + + var serverOptions = { + module: serverPath //node should fork this + }; + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + var options = { + serverOptions: getServerOptions() + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/FeatureClient/main.js b/test/spec/LanguageTools-test-files/clients/FeatureClient/main.js new file mode 100644 index 00000000000..8243d14fd5f --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/FeatureClient/main.js @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "FeatureClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(function () { + return client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions" + }); + }).then(function () { + retval.resolve(); + }); + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js new file mode 100644 index 00000000000..15ae4c8de3d --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/client.js @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + clientName = "InterfaceTestClient", + client = null; + +function notificationMethod(params) { + switch (params.action) { + case 'acknowledgement': + { + client._notifyBrackets({ + type: "acknowledge", + params: { + acknowledgement: true, + clientName: clientName + } + }); + break; + } + case 'nodeSyncRequest': + { + var syncRequest = client._requestBrackets({ + type: "nodeSyncRequest", + params: { + syncRequest: true, + clientName: clientName + } + }); + + syncRequest.then(function (value) { + client._notifyBrackets({ + type: "validateSyncRequest", + params: { + syncRequestResult: value, + clientName: clientName + } + }); + }); + break; + } + case 'nodeAsyncRequestWhichResolves': + { + var asyncRequestS = client._requestBrackets({ + type: "nodeAsyncRequestWhichResolves", + params: { + asyncRequest: true, + clientName: clientName + } + }); + + asyncRequestS.then(function (value) { + client._notifyBrackets({ + type: "validateAsyncSuccess", + params: { + asyncRequestResult: value, + clientName: clientName + } + }); + }); + break; + } + case 'nodeAsyncRequestWhichFails': + { + var asyncRequestE = client._requestBrackets({ + type: "nodeAsyncRequestWhichFails", + params: { + asyncRequest: true, + clientName: clientName + } + }); + + asyncRequestE.catch(function (value) { + client._notifyBrackets({ + type: "validateAsyncFail", + params: { + asyncRequestError: value, + clientName: clientName + } + }); + }); + break; + } + } +} + +function requestMethod(params) { + switch (params.action) { + case 'resolve': + { + return Promise.resolve("resolved"); + } + case 'reject': + { + return Promise.reject("rejected"); + } + } +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnNotificationHandler("notificationMethod", notificationMethod); + client.addOnRequestHandler('requestMethod', requestMethod); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js new file mode 100644 index 00000000000..5889e5a3b37 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/InterfaceTestClient/main.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "InterfaceTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + retval.resolve(); + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js new file mode 100644 index 00000000000..9dd77848740 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/client.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ + +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + clientName = "LoadSimpleClient", + client = null; + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js new file mode 100644 index 00000000000..c4160a98cfe --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/LoadSimpleClient/main.js @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "LoadSimpleClient", + clientPromise = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(retval.resolve, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js new file mode 100644 index 00000000000..2f5c22c2f64 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/client.js @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + clientName = "ModuleTestClient", + client = null, + modulePath = null, + relativeLSPathArray = ["..", "..", "server", "lsp-test-server", "main.js"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\"; + +function getServerOptions() { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + serverPath = path.resolve(serverPath); + + var serverOptions = { + module: serverPath //node should fork this + }; + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + var options = { + serverOptions: getServerOptions() + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js new file mode 100644 index 00000000000..b3507f9d887 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/ModuleTestClient/main.js @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "ModuleTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(function () { + return client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions" + }); + }).then(function () { + retval.resolve(); + }); + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js new file mode 100644 index 00000000000..d038105cae0 --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/client.js @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ + +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + path = require("path"), + cp = require("child_process"), + clientName = "OptionsTestClient", + client = null, + modulePath = null, + relativeLSPathArray = ["..", "..", "server", "lsp-test-server"], + FORWARD_SLASH = "/", + BACKWARD_SLASH = "\\"; + +function getServerOptions(type) { + var serverPath = modulePath.split(BACKWARD_SLASH) + .join(FORWARD_SLASH).split(FORWARD_SLASH).concat(relativeLSPathArray) + .join(FORWARD_SLASH); + + var newEnv = process.env; + newEnv.CUSTOMENVVARIABLE = "ANYTHING"; + + serverPath = path.resolve(serverPath); + var serverOptions = null; + + switch (type) { + case 'runtime': + { + // [runtime] [execArgs] [module] [args (with communication args)] (with options[env, cwd]) + serverOptions = { + runtime: process.execPath, //Path to node but could be anything, like php or perl + module: "main.js", + args: [ + "--server-args" //module args + ], //Arguments to process + options: { + cwd: serverPath, //The current directory where main.js is located + env: newEnv, //The process will be started CUSTOMENVVARIABLE in its environment + execArgv: [ + "--no-warnings", + "--no-deprecation" //runtime executable args + ] + }, + communication: "ipc" + }; + break; + } + case 'function': + { + serverOptions = function () { + return new Promise(function (resolve, reject) { + var serverProcess = cp.spawn(process.execPath, [ + "main.js", + "--stdio" //Have to add communication args manually + ], { + cwd: serverPath + }); + + if (serverProcess && serverProcess.pid) { + resolve({ + process: serverProcess + }); + } else { + reject("Couldn't create server process"); + } + }); + }; + break; + } + case 'command': + { + // [command] [args] (with options[env, cwd]) + serverOptions = { + command: process.execPath, //Path to executable, mostly runtime + args: [ + "--no-warnings", + "--no-deprecation", + "main.js", + "--stdio", //Have to add communication args manually + "--server-args" + ], //Arguments to process, ORDER WILL MATTER + options: { + cwd: serverPath, + env: newEnv //The process will be started CUSTOMENVVARIABLE in its environment + } + }; + break; + } + } + + return serverOptions; +} + +function setModulePath(params) { + modulePath = params.modulePath.slice(0, params.modulePath.length - 1); + + return Promise.resolve(); +} + +function setOptions(params) { + if (!params || !params.optionsType) { + return Promise.reject("Can't start server because no options type provided"); + } + + var oType = params.optionsType, + options = { + serverOptions: getServerOptions(oType) + }; + + client.setOptions(options); + + return Promise.resolve("Server options set successfully"); +} + +function init(domainManager) { + client = new LanguageClient(clientName, domainManager); + client.addOnRequestHandler('setModulePath', setModulePath); + client.addOnRequestHandler('setOptions', setOptions); +} + +exports.init = init; diff --git a/test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js new file mode 100644 index 00000000000..67610b9f0ed --- /dev/null +++ b/test/spec/LanguageTools-test-files/clients/OptionsTestClient/main.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "OptionsTestClient", + clientPromise = null, + client = null; + + AppInit.appReady(function () { + clientPromise = LanguageTools.initiateToolingService(clientName, clientFilePath, ['unknown']); + }); + + exports.initExtension = function () { + var retval = $.Deferred(); + + if ($.isFunction(clientPromise.promise)) { + clientPromise.then(function (textClient) { + client = textClient; + + client.sendCustomRequest({ + messageType: "brackets", + type: "setModulePath", + params: { + modulePath: ExtensionUtils.getModulePath(module) + } + }).then(retval.resolve); + + + }, retval.reject); + } else { + retval.reject(); + } + + return retval; + }; + + exports.getClient = function () { + return client; + }; +}); diff --git a/test/spec/LanguageTools-test-files/project/sample1.txt b/test/spec/LanguageTools-test-files/project/sample1.txt new file mode 100644 index 00000000000..8de75dcb4d7 --- /dev/null +++ b/test/spec/LanguageTools-test-files/project/sample1.txt @@ -0,0 +1 @@ +This has some text. \ No newline at end of file diff --git a/test/spec/LanguageTools-test-files/project/sample2.txt b/test/spec/LanguageTools-test-files/project/sample2.txt new file mode 100644 index 00000000000..9289cdf2601 --- /dev/null +++ b/test/spec/LanguageTools-test-files/project/sample2.txt @@ -0,0 +1 @@ +This has error text. \ No newline at end of file diff --git a/test/spec/LanguageTools-test-files/server/lsp-test-server/main.js b/test/spec/LanguageTools-test-files/server/lsp-test-server/main.js new file mode 100644 index 00000000000..2e0358eab1d --- /dev/null +++ b/test/spec/LanguageTools-test-files/server/lsp-test-server/main.js @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +/*eslint indent: 0*/ +'use strict'; + +var vls = require("vscode-languageserver"), + connection = vls.createConnection(vls.ProposedFeatures.all); + +connection.onInitialize(function (params) { + return { + capabilities: { + textDocumentSync: 1, + completionProvider: { + resolveProvider: true, + triggerCharacters: [ + '=', + ' ', + '$', + '-', + '&' + ] + }, + definitionProvider: true, + signatureHelpProvider: { + triggerCharacters: [ + '-', + '[', + ',', + ' ', + '=' + ] + }, + "workspaceSymbolProvider": "true", + "documentSymbolProvider": "true", + "referencesProvider": "true" + } + }; +}); + +connection.onInitialized(function () { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.InitializedNotification.type._method + } + }); + + connection.workspace.onDidChangeWorkspaceFolders(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidChangeWorkspaceFoldersNotification.type._method, + params: params + } + }); + }); +}); + +connection.onCompletion(function (params) { + return { + received: { + type: vls.CompletionRequest.type._method, + params: params + } + }; +}); + +connection.onSignatureHelp(function (params) { + return { + received: { + type: vls.SignatureHelpRequest.type._method, + params: params + } + }; +}); + +connection.onCompletionResolve(function (params) { + return { + received: { + type: vls.CompletionResolveRequest.type._method, + params: params + } + }; +}); + +connection.onDefinition(function (params) { + return { + received: { + type: vls.DefinitionRequest.type._method, + params: params + } + }; +}); + +connection.onDeclaration(function (params) { + return { + received: { + type: vls.DeclarationRequest.type._method, + params: params + } + }; +}); + +connection.onImplementation(function (params) { + return { + received: { + type: vls.ImplementationRequest.type._method, + params: params + } + }; +}); + +connection.onDocumentSymbol(function (params) { + return { + received: { + type: vls.DocumentSymbolRequest.type._method, + params: params + } + }; +}); + +connection.onWorkspaceSymbol(function (params) { + return { + received: { + type: vls.WorkspaceSymbolRequest.type._method, + params: params + } + }; +}); + +connection.onDidOpenTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidOpenTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onDidChangeTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidChangeTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onDidCloseTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidCloseTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onDidSaveTextDocument(function (params) { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: vls.DidSaveTextDocumentNotification.type._method, + params: params + } + }); +}); + +connection.onNotification(function (type, params) { + switch (type) { + case "custom/triggerDiagnostics": + { + connection.sendDiagnostics({ + received: { + type: type, + params: params + } + }); + break; + } + case "custom/getNotification": + { + connection.sendNotification("custom/serverNotification", { + received: { + type: type, + params: params + } + }); + break; + } + case "custom/getRequest": + { + connection.sendRequest("custom/serverRequest", { + received: { + type: type, + params: params + } + }).then(function (resolveResponse) { + connection.sendNotification("custom/requestSuccessNotification", { + received: { + type: "custom/requestSuccessNotification", + params: resolveResponse + } + }); + }).catch(function (rejectResponse) { + connection.sendNotification("custom/requestFailedNotification", { + received: { + type: "custom/requestFailedNotification", + params: rejectResponse + } + }); + }); + break; + } + default: + { + connection.sendNotification(vls.LogMessageNotification.type, { + received: { + type: type, + params: params + } + }); + } + } +}); + +connection.onRequest(function (type, params) { + return { + received: { + type: type, + params: params + } + }; +}); + +// Listen on the connection +connection.listen(); diff --git a/test/spec/LanguageTools-test-files/server/lsp-test-server/package.json b/test/spec/LanguageTools-test-files/server/lsp-test-server/package.json new file mode 100644 index 00000000000..17b9af0423e --- /dev/null +++ b/test/spec/LanguageTools-test-files/server/lsp-test-server/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "vscode-languageserver": "^5.3.0-next.1" + } +} diff --git a/test/spec/LanguageTools-test.js b/test/spec/LanguageTools-test.js new file mode 100644 index 00000000000..0b80a3db32d --- /dev/null +++ b/test/spec/LanguageTools-test.js @@ -0,0 +1,1599 @@ +/* + * Copyright (c) 2019 - present Adobe. 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 regexp: true */ +/*global describe, it, expect, spyOn, runs, waitsForDone, waitsForFail, afterEach */ +/*eslint indent: 0*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + 'use strict'; + + // Load dependent modules + var ExtensionLoader = require("utils/ExtensionLoader"), + SpecRunnerUtils = require("spec/SpecRunnerUtils"), + LanguageClientWrapper = require("languageTools/LanguageClientWrapper"), + LanguageTools = require("languageTools/LanguageTools"), + EventDispatcher = require("utils/EventDispatcher"), + ToolingInfo = JSON.parse(brackets.getModule("text!languageTools/ToolingInfo.json")); + + var testPath = SpecRunnerUtils.getTestPath("/spec/LanguageTools-test-files"), + serverResponse = { + capabilities: { + textDocumentSync: 1, + completionProvider: { + resolveProvider: true, + triggerCharacters: [ + '=', + ' ', + '$', + '-', + '&' + ] + }, + definitionProvider: true, + signatureHelpProvider: { + triggerCharacters: [ + '-', + '[', + ',', + ' ', + '=' + ] + }, + "workspaceSymbolProvider": "true", + "documentSymbolProvider": "true", + "referencesProvider": "true" + } + }; + + describe("LanguageTools", function () { + function loadClient(name) { + var config = { + baseUrl: testPath + "/clients/" + name + }; + + return ExtensionLoader.loadExtension(name, config, "main"); + } + + function getExtensionFromContext(name) { + var extensionContext = brackets.libRequire.s.contexts[name]; + + return extensionContext && extensionContext.defined && extensionContext.defined.main; + } + + it("should load a simple test client extension", function () { + var promise, + consoleErrors = []; + + runs(function () { + var originalConsoleErrorFn = console.error; + spyOn(console, "error").andCallFake(function () { + originalConsoleErrorFn.apply(console, arguments); + + if (typeof arguments[0] === "string" && + arguments[0].includes("Error loading domain \"LoadSimpleClient\"")) { + consoleErrors.push(Array.prototype.join.call(arguments)); + } + }); + + promise = loadClient("LoadSimpleClient"); + + waitsForDone(promise, "loadClient"); + }); + + runs(function () { + expect(consoleErrors).toEqual([]); + expect(promise.state()).toBe("resolved"); + }); + }); + + describe("Brackets & Node Communication", function () { + var intefacePromise, + extension, + client; + + it("should load the interface client extension", function () { + runs(function () { + intefacePromise = loadClient("InterfaceTestClient"); + intefacePromise.done(function () { + extension = getExtensionFromContext("InterfaceTestClient"); + client = extension.getClient(); + }); + + waitsForDone(intefacePromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("InterfaceTestClient"); + }); + }); + + it("should receive acknowledgement notification after sending notification to node", function () { + var notificationStatus = false; + + function notifyWithPromise() { + var retval = $.Deferred(); + + client._addOnNotificationHandler("acknowledge", function (params) { + if (params.clientName === "InterfaceTestClient" && params.acknowledgement) { + notificationStatus = true; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "acknowledgement" + } + }); + + return retval; + } + + runs(function () { + var notificationPromise = notifyWithPromise(); + + waitsForDone(notificationPromise, "NotificationInterface"); + }); + + runs(function () { + expect(notificationStatus).toBe(true); + }); + }); + + it("should send request to node which should resolve", function () { + var result = null; + + function requestWithPromise() { + return client.sendCustomRequest({ + messageType: "brackets", + type: "requestMethod", + params: { + action: "resolve" + } + }); + } + + runs(function () { + var requestPromise = requestWithPromise(); + requestPromise.done(function (returnVal) { + result = returnVal; + }); + + waitsForDone(requestPromise, "RequestInterface"); + }); + + runs(function () { + expect(result).toBe("resolved"); + }); + }); + + it("should send request to node which should reject", function () { + var result = null; + + function requestWithPromise() { + return client.sendCustomRequest({ + messageType: "brackets", + type: "requestMethod", + params: { + action: "reject" + } + }); + } + + runs(function () { + var requestPromise = requestWithPromise(); + requestPromise.fail(function (returnVal) { + result = returnVal; + }); + + waitsForFail(requestPromise, "RequestInterface"); + }); + + runs(function () { + expect(result).toBe("rejected"); + }); + }); + + it("should handle sync request from node side", function () { + var requestResult = null; + + function nodeRequestWithPromise() { + var retval = $.Deferred(); + + client._addOnRequestHandler("nodeSyncRequest", function (params) { + if (params.clientName === "InterfaceTestClient" && params.syncRequest) { + //We return value directly since it is a sync request + return "success"; + } + }); + + //trigger request from node side + client._addOnNotificationHandler("validateSyncRequest", function (params) { + if (params.clientName === "InterfaceTestClient" && params.syncRequestResult) { + requestResult = params.syncRequestResult; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "nodeSyncRequest" + } + }); + + return retval; + } + + runs(function () { + var nodeRequestPromise = nodeRequestWithPromise(); + + waitsForDone(nodeRequestPromise, "NodeRequestInterface"); + }); + + runs(function () { + expect(requestResult).toEqual("success"); + }); + }); + + it("should handle async request from node side which is resolved", function () { + var requestResult = null; + + function nodeRequestWithPromise() { + var retval = $.Deferred(); + + client._addOnRequestHandler("nodeAsyncRequestWhichResolves", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequest) { + //We return promise which can be resolved in async + return $.Deferred().resolve("success"); + } + }); + + //trigger request from node side + client._addOnNotificationHandler("validateAsyncSuccess", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequestResult) { + requestResult = params.asyncRequestResult; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "nodeAsyncRequestWhichResolves" + } + }); + + return retval; + } + + runs(function () { + var nodeRequestPromise = nodeRequestWithPromise(); + + waitsForDone(nodeRequestPromise, "NodeRequestInterface"); + }); + + runs(function () { + expect(requestResult).toEqual("success"); + }); + }); + + it("should handle async request from node side which fails", function () { + var requestResult = null; + + function nodeRequestWithPromise() { + var retval = $.Deferred(); + + client._addOnRequestHandler("nodeAsyncRequestWhichFails", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequest) { + //We return promise which can be resolved in async + return $.Deferred().reject("error"); + } + }); + + //trigger request from node side + client._addOnNotificationHandler("validateAsyncFail", function (params) { + if (params.clientName === "InterfaceTestClient" && params.asyncRequestError) { + requestResult = params.asyncRequestError; + retval.resolve(); + } + }); + + client.sendCustomNotification({ + messageType: "brackets", + type: "notificationMethod", + params: { + action: "nodeAsyncRequestWhichFails" + } + }); + + return retval; + } + + runs(function () { + var nodeRequestPromise = nodeRequestWithPromise(); + + waitsForDone(nodeRequestPromise, "NodeRequestInterface"); + }); + + runs(function () { + expect(requestResult).toEqual("error"); + }); + }); + }); + + describe("Client Start and Stop Tests", function () { + var projectPath = testPath + "/project", + optionsPromise, + extension, + client = null; + + it("should start a simple module based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("ModuleTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("ModuleTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("ModuleTestClient"); + + startPromise = client.start({ + rootPath: projectPath + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should stop a simple module based client", function () { + var restartPromise, + restartStatus = false; + + runs(function () { + if (client) { + restartPromise = client.stop().done(function () { + return client.start({ + rootPath: projectPath + }); + }); + restartPromise.done(function () { + restartStatus = true; + }); + } + + waitsForDone(restartPromise, "RestartClient"); + }); + + runs(function () { + expect(restartStatus).toBe(true); + }); + }); + + + it("should stop a simple module based client", function () { + var stopPromise, + stopStatus = false; + + runs(function () { + if (client) { + stopPromise = client.stop(); + stopPromise.done(function () { + stopStatus = true; + client = null; + }); + } + + waitsForDone(stopPromise, "StopClient"); + }); + + runs(function () { + expect(stopStatus).toBe(true); + }); + }); + }); + + describe("Language Server Spawn Schemes", function () { + var projectPath = testPath + "/project", + optionsPromise, + extension, + client = null; + + afterEach(function () { + var stopPromise, + stopStatus = false; + + runs(function () { + if (client) { + stopPromise = client.stop(); + stopPromise.done(function () { + stopStatus = true; + client = null; + }); + } else { + stopStatus = true; + } + + waitsForDone(stopPromise, "StopClient"); + }); + + runs(function () { + expect(stopStatus).toBe(true); + }); + }); + + it("should start a simple module based client with node-ipc", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + communicationType: "ipc" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple module based client with stdio", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + communicationType: "stdio" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple module based client with pipe", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + communicationType: "pipe" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple module based client with socket", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("CommunicationTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("CommunicationTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("CommunicationTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptionsForSocket" + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple runtime based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("OptionsTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("OptionsTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("OptionsTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + optionsType: "runtime" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple function based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("OptionsTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("OptionsTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("OptionsTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + optionsType: "function" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should start a simple command based client", function () { + var startResult = false, + startPromise; + + runs(function () { + optionsPromise = loadClient("OptionsTestClient"); + optionsPromise.done(function () { + extension = getExtensionFromContext("OptionsTestClient"); + client = extension.getClient(); + }); + + waitsForDone(optionsPromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("OptionsTestClient"); + + startPromise = client.sendCustomRequest({ + messageType: "brackets", + type: "setOptions", + params: { + optionsType: "command" + } + }).then(function () { + return client.start({ + rootPath: projectPath + }); + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + }); + + describe("Parameter validation for client based communication", function () { + var requestValidator = LanguageClientWrapper.validateRequestParams, + notificationValidator = LanguageClientWrapper.validateNotificationParams; + + var paramTemplateA = { + rootPath: "somePath" + }; + + var paramTemplateB = { + filePath: "somePath", + cursorPos: { + line: 1, + ch: 1 + } + }; + + var paramTemplateC = { + filePath: "somePath" + }; + + var paramTemplateD = { + filePath: "something", + fileContent: "something", + languageId: "something" + }; + + var paramTemplateE = { + filePath: "something", + fileContent: "something" + }; + + var paramTemplateF = { + foldersAdded: ["added"], + foldersRemoved: ["removed"] + }; + + it("should validate the params for request: client.start", function () { + var params = Object.assign({}, paramTemplateA), + retval = requestValidator(ToolingInfo.LANGUAGE_SERVICE.START, params); + + var params2 = Object.assign({}, paramTemplateA); + params2["capabilities"] = { + feature: true + }; + var retval2 = requestValidator(ToolingInfo.LANGUAGE_SERVICE.START, params2); + + expect(retval).toEqual({ + rootPath: "somePath", + capabilities: false + }); + + expect(retval2).toEqual({ + rootPath: "somePath", + capabilities: { + feature: true + } + }); + }); + + it("should invalidate the params for request: client.start", function () { + var retval = requestValidator(ToolingInfo.LANGUAGE_SERVICE.START, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.{requestHints, requestParameterHints, gotoDefinition}", function () { + var params = Object.assign({}, paramTemplateB), + retval = requestValidator(ToolingInfo.FEATURES.CODE_HINTS, params); + + expect(retval).toEqual(paramTemplateB); + }); + + it("should invalidate the params for request: client.{requestHints, requestParameterHints, gotoDefinition}", function () { + var retval = requestValidator(ToolingInfo.FEATURES.CODE_HINTS, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.findReferences", function () { + var params = Object.assign({}, paramTemplateB), + retval = requestValidator(ToolingInfo.FEATURES.FIND_REFERENCES, params); + + var result = Object.assign({}, paramTemplateB); + result["includeDeclaration"] = false; + + expect(retval).toEqual(result); + }); + + it("should invalidate the params for request: client.findReferences", function () { + var retval = requestValidator(ToolingInfo.FEATURES.FIND_REFERENCES, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.requestSymbolsForDocument", function () { + var params = Object.assign({}, paramTemplateC), + retval = requestValidator(ToolingInfo.FEATURES.DOCUMENT_SYMBOLS, params); + + expect(retval).toEqual(paramTemplateC); + }); + + it("should invalidate the params for request: client.requestSymbolsForDocument", function () { + var retval = requestValidator(ToolingInfo.FEATURES.DOCUMENT_SYMBOLS, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for request: client.requestSymbolsForWorkspace", function () { + var params = Object.assign({}, { + query: 'a' + }), + retval = requestValidator(ToolingInfo.FEATURES.PROJECT_SYMBOLS, params); + + expect(retval).toEqual({ + query: 'a' + }); + }); + + it("should invalidate the params for request: client.requestSymbolsForWorkspace", function () { + var retval = requestValidator(ToolingInfo.FEATURES.PROJECT_SYMBOLS, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.notifyTextDocumentOpened", function () { + var params = Object.assign({}, paramTemplateD), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED, params); + + expect(retval).toEqual(paramTemplateD); + }); + + it("should invalidate the params for notification: client.notifyTextDocumentOpened", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_OPENED, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.notifyTextDocumentChanged", function () { + var params = Object.assign({}, paramTemplateE), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED, params); + + expect(retval).toEqual(paramTemplateE); + }); + + it("should invalidate the params for notification: client.notifyTextDocumentChanged", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_CHANGED, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.{notifyTextDocumentClosed, notifyTextDocumentSave}", function () { + var params = Object.assign({}, paramTemplateC), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED, params); + + expect(retval).toEqual(paramTemplateC); + }); + + it("should invalidate the params for notification: client.{notifyTextDocumentClosed, notifyTextDocumentSave}", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.DOCUMENT_SAVED, {}); + + expect(retval).toBeNull(); + }); + + it("should validate the params for notification: client.notifyProjectRootsChanged", function () { + var params = Object.assign({}, paramTemplateF), + retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED, params); + + expect(retval).toEqual(paramTemplateF); + }); + + it("should invalidate the params for notification: client.notifyProjectRootsChanged", function () { + var retval = notificationValidator(ToolingInfo.SYNCHRONIZE_EVENTS.PROJECT_FOLDERS_CHANGED, {}); + + expect(retval).toBeNull(); + }); + + it("should passthrough the params for request: client.sendCustomRequest", function () { + var params = Object.assign({}, { + a: 1, + b: 2 + }), + retval = requestValidator(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_REQUEST, params); + + expect(retval).toEqual({ + a: 1, + b: 2 + }); + }); + + it("should passthrough the params for notification: client.sendCustomNotification", function () { + var params = Object.assign({}, { + a: 1, + b: 2 + }), + retval = notificationValidator(ToolingInfo.LANGUAGE_SERVICE.CUSTOM_NOTIFICATION, params); + + expect(retval).toEqual({ + a: 1, + b: 2 + }); + }); + + it("should passthrough the params for any request if format is 'lsp'", function () { + var params = Object.assign({}, { + format: 'lsp', + a: 1, + b: 2 + }), + retval = requestValidator("AnyType", params); + + expect(retval).toEqual({ + format: 'lsp', + a: 1, + b: 2 + }); + }); + + it("should passthrough the params for any notification if format is 'lsp'", function () { + var params = Object.assign({}, { + format: 'lsp', + a: 1, + b: 2 + }), + retval = notificationValidator("AnyType", params); + + expect(retval).toEqual({ + format: 'lsp', + a: 1, + b: 2 + }); + }); + }); + + describe("Test LSP Request and Notifications", function () { + var projectPath = testPath + "/project", + featurePromise, + extension, + client = null, + docPath1 = projectPath + "/sample1.txt", + docPath2 = projectPath + "/sample2.txt", + pos = { + line: 1, + ch: 2 + }, + fileContent = "some content", + languageId = "unknown"; + + function createPromiseForNotification(type) { + var promise = $.Deferred(); + + switch (type) { + case "textDocument/publishDiagnostics": { + client.addOnCodeInspection(function (params) { + promise.resolve(params); + }); + break; + } + case "custom/serverNotification": + case "custom/requestSuccessNotification": + case "custom/requestFailedNotification": + { + client.onCustomNotification(type, function (params) { + promise.resolve(params); + }); + break; + } + default: { + client.addOnLogMessage(function (params) { + if (params.received && params.received.type && + params.received.type === type) { + promise.resolve(params); + } + }); + } + } + + return promise; + } + + it("should successfully start client", function () { + var startResult = false, + startPromise; + + runs(function () { + featurePromise = loadClient("FeatureClient"); + featurePromise.done(function () { + extension = getExtensionFromContext("FeatureClient"); + client = extension.getClient(); + }); + + waitsForDone(featurePromise); + }); + + runs(function () { + expect(client).toBeTruthy(); + expect(client._name).toEqual("FeatureClient"); + + client.onDynamicCapabilityRegistration(function () { + return $.Deferred().resolve(); + }); + + client.onDynamicCapabilityUnregistration(function () { + return $.Deferred().resolve(); + }); + + startPromise = client.start({ + rootPath: projectPath + }); + + startPromise.done(function (capabilities) { + startResult = capabilities; + }); + + waitsForDone(startPromise, "StartClient"); + }); + + runs(function () { + expect(startResult).toBeTruthy(); + expect(startResult).toEqual(serverResponse); + }); + }); + + it("should successfully requestHints with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestHints({ + filePath: docPath1, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully passthrough params with lsp format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestHints({ + format: 'lsp', + textDocument: { + uri: 'file:///somepath/project/sample1.txt' + }, + position: { + line: 1, + character: 2 + } + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse).toEqual({ + received: { + type: 'textDocument/completion', + params: { + textDocument: { + uri: 'file:///somepath/project/sample1.txt' + }, + position: { + line: 1, + character: 2 + } + } + } + }); + }); + }); + + it("should successfully getAdditionalInfoForHint", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.getAdditionalInfoForHint({ + hintItem: true + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse).toEqual({ + received: { + type: 'completionItem/resolve', + params: { + hintItem: true + } + } + }); + }); + }); + + it("should successfully requestParameterHints with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestParameterHints({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully gotoDefinition with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.gotoDefinition({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully gotoImplementation with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.gotoImplementation({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully gotoDeclaration with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.gotoDeclaration({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully findReferences with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.findReferences({ + filePath: docPath2, + cursorPos: pos + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully requestSymbolsForDocument with brackets format", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestSymbolsForDocument({ + filePath: docPath2 + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully requestSymbolsForWorkspace", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.requestSymbolsForWorkspace({ + query: "s" + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully sendCustomRequest to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = client.sendCustomRequest({ + type: "custom/serverRequest", + params: { + anyParam: true + } + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentOpened to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didOpen"); + client.notifyTextDocumentOpened({ + languageId: languageId, + filePath: docPath1, + fileContent: fileContent + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentClosed to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didClose"); + client.notifyTextDocumentClosed({ + filePath: docPath1 + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentSave to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didSave"); + client.notifyTextDocumentSave({ + filePath: docPath2 + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyTextDocumentChanged to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/didChange"); + client.notifyTextDocumentChanged({ + filePath: docPath2, + fileContent: fileContent + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully notifyProjectRootsChanged to server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("workspace/didChangeWorkspaceFolders"); + client.notifyProjectRootsChanged({ + foldersAdded: ["path1", "path2"], + foldersRemoved: ["path3"] + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully get send custom notification to trigger diagnostics from server", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + requestPromise = createPromiseForNotification("textDocument/publishDiagnostics"); + client.sendCustomNotification({ + type: "custom/triggerDiagnostics" + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully create a custom event trigger for server notification", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + EventDispatcher.makeEventDispatcher(exports); + LanguageTools.listenToCustomEvent(exports, "triggerDiagnostics"); + client.addOnCustomEventHandler("triggerDiagnostics", function () { + client.sendCustomNotification({ + type: "custom/triggerDiagnostics" + }); + }); + requestPromise = createPromiseForNotification("textDocument/publishDiagnostics"); + exports.trigger("triggerDiagnostics"); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully handle a custom server notification", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + + requestPromise = createPromiseForNotification("custom/serverNotification"); + client.sendCustomNotification({ + type: "custom/getNotification" + }); + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerNotification"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully handle a custom server request on resolve", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + + requestPromise = createPromiseForNotification("custom/requestSuccessNotification"); + client.onCustomRequest("custom/serverRequest", function (params) { + return $.Deferred().resolve(params); + }); + + client.sendCustomNotification({ + type: "custom/getRequest" + }); + + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully handle a custom server request on reject", function () { + var requestPromise, + requestResponse = null; + + runs(function () { + + requestPromise = createPromiseForNotification("custom/requestFailedNotification"); + client.onCustomRequest("custom/serverRequest", function (params) { + return $.Deferred().reject(params); + }); + + client.sendCustomNotification({ + type: "custom/getRequest" + }); + + requestPromise.done(function (response) { + requestResponse = response; + }); + + waitsForDone(requestPromise, "ServerRequest"); + }); + + runs(function () { + expect(requestResponse.received).toBeTruthy(); + }); + }); + + it("should successfully stop client", function () { + var stopPromise, + stopStatus = false; + + runs(function () { + if (client) { + stopPromise = client.stop(); + stopPromise.done(function () { + stopStatus = true; + client = null; + }); + } + + waitsForDone(stopPromise, "StopClient"); + }); + + runs(function () { + expect(stopStatus).toBe(true); + }); + }); + }); + }); +}); From b977dff1d7171253c3dfd35c69aeb40686b7b537 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Wed, 3 Apr 2019 11:46:26 +0530 Subject: [PATCH 055/149] Revert "Language Server Protocol Support for Brackets - Fix Author" (#14680) From 9516c8de26b44dbf5a0f3bc9a6ccf22614bb9186 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Wed, 3 Apr 2019 14:12:03 +0530 Subject: [PATCH 056/149] Php Tooling Extensions Using LSP Framework (#14671) * Php Tooling Extensions Using LSP Framework * Corrected Indentation space * Corrected ESLint Error * addressed review comments * Addressed review comments * addressed review comments * Added Preferences description string * Addressed review comments * Addressed review comments * addressed review comments * Addresed review comments * Addresed review comments * Addressed some bug * Adding Unit test Files * Corrected the strings * Fixed Eslint Errors * Added some unit test Cases * using restart function * Switch to php7 in travis before dependency installation php-Tooling npm package requires felixfbecker/language-server php package, which can only be installed on php7. this should fix the break in brackets travis build --- .travis.yml | 2 + .../default/PhpTooling/CodeHintsProvider.js | 160 ++++++ src/extensions/default/PhpTooling/client.js | 120 +++++ .../default/PhpTooling/composer.json | 7 + src/extensions/default/PhpTooling/main.js | 231 ++++++++ .../default/PhpTooling/package.json | 14 + .../default/PhpTooling/phpGlobals.json | 75 +++ .../PhpTooling/unittest-files/mac/invalidphp | Bin 0 -> 10292 bytes .../PhpTooling/unittest-files/test/test1.php | 2 + .../PhpTooling/unittest-files/test/test2.php | 30 ++ .../PhpTooling/unittest-files/test/test3.php | 10 + .../unittest-files/win/invalidphp.exe | Bin 0 -> 97280 bytes .../default/PhpTooling/unittests.js | 499 ++++++++++++++++++ src/nls/root/strings.js | 15 +- 14 files changed, 1163 insertions(+), 2 deletions(-) create mode 100644 src/extensions/default/PhpTooling/CodeHintsProvider.js create mode 100644 src/extensions/default/PhpTooling/client.js create mode 100644 src/extensions/default/PhpTooling/composer.json create mode 100755 src/extensions/default/PhpTooling/main.js create mode 100644 src/extensions/default/PhpTooling/package.json create mode 100644 src/extensions/default/PhpTooling/phpGlobals.json create mode 100755 src/extensions/default/PhpTooling/unittest-files/mac/invalidphp create mode 100644 src/extensions/default/PhpTooling/unittest-files/test/test1.php create mode 100644 src/extensions/default/PhpTooling/unittest-files/test/test2.php create mode 100644 src/extensions/default/PhpTooling/unittest-files/test/test3.php create mode 100755 src/extensions/default/PhpTooling/unittest-files/win/invalidphp.exe create mode 100644 src/extensions/default/PhpTooling/unittests.js diff --git a/.travis.yml b/.travis.yml index 464408502fc..b2803e1d928 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: node_js sudo: false # use container-based Travis infrastructure node_js: - "6" +before_install: + - phpenv global 7.0 #switch to php7, since that's what php-Tooling extension requires before_script: - npm install -g grunt-cli - npm install -g jasmine-node diff --git a/src/extensions/default/PhpTooling/CodeHintsProvider.js b/src/extensions/default/PhpTooling/CodeHintsProvider.js new file mode 100644 index 00000000000..937f26476eb --- /dev/null +++ b/src/extensions/default/PhpTooling/CodeHintsProvider.js @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/* eslint-disable indent */ +/* eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var _ = brackets.getModule("thirdparty/lodash"); + + var DefaultProviders = brackets.getModule("languageTools/DefaultProviders"), + EditorManager = brackets.getModule('editor/EditorManager'), + TokenUtils = brackets.getModule("utils/TokenUtils"), + StringMatch = brackets.getModule("utils/StringMatch"), + matcher = new StringMatch.StringMatcher({ + preferPrefixMatches: true + }); + + var phpSuperGlobalVariables = JSON.parse(require("text!phpGlobals.json")); + + function CodeHintsProvider(client) { + this.defaultCodeHintProviders = new DefaultProviders.CodeHintsProvider(client); + } + + function formatTypeDataForToken($hintObj, token) { + $hintObj.addClass('brackets-hints-with-type-details'); + if (token.detail) { + if (token.detail.trim() !== '?') { + if (token.detail.length < 30) { + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("brackets-hints-type-details"); + } + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("hint-description"); + } + } else { + if (token.keyword) { + $('keyword').appendTo($hintObj).addClass("brackets-hints-keyword"); + } + } + if (token.documentation) { + $hintObj.attr('title', token.documentation); + $('').text(token.documentation.trim()).appendTo($hintObj).addClass("hint-doc"); + } + } + + function filterWithQueryAndMatcher(hints, query) { + var matchResults = $.map(hints, function (hint) { + var searchResult = matcher.match(hint.label, query); + if (searchResult) { + for (var key in hint) { + searchResult[key] = hint[key]; + } + } + + return searchResult; + }); + + return matchResults; + } + + CodeHintsProvider.prototype.hasHints = function (editor, implicitChar) { + return this.defaultCodeHintProviders.hasHints(editor, implicitChar); + }; + + CodeHintsProvider.prototype.getHints = function (implicitChar) { + if (!this.defaultCodeHintProviders.client) { + return null; + } + + var editor = EditorManager.getActiveEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + $deferredHints = $.Deferred(), + self = this.defaultCodeHintProviders; + + this.defaultCodeHintProviders.client.requestHints({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + var context = TokenUtils.getInitialContext(editor._codeMirror, pos), + hints = []; + + self.query = context.token.string.slice(0, context.pos.ch - context.token.start); + if (msgObj) { + var res = msgObj.items || []; + // There is a bug in Php Language Server, Php Language Server does not provide superGlobals + // Variables as completion. so these variables are being explicity put in response objects + // below code should be removed if php server fix this bug. + if(self.query) { + for(var key in phpSuperGlobalVariables) { + res.push({ + label: key, + documentation: phpSuperGlobalVariables[key].description, + detail: phpSuperGlobalVariables[key].type + }); + } + } + + var filteredHints = filterWithQueryAndMatcher(res, self.query); + + StringMatch.basicMatchSort(filteredHints); + filteredHints.forEach(function (element) { + var $fHint = $("") + .addClass("brackets-hints"); + + if (element.stringRanges) { + element.stringRanges.forEach(function (item) { + if (item.matched) { + $fHint.append($("") + .append(_.escape(item.text)) + .addClass("matched-hint")); + } else { + $fHint.append(_.escape(item.text)); + } + }); + } else { + $fHint.text(element.label); + } + + $fHint.data("token", element); + formatTypeDataForToken($fHint, element); + hints.push($fHint); + }); + } + + $deferredHints.resolve({ + "hints": hints + }); + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + CodeHintsProvider.prototype.insertHint = function ($hint) { + return this.defaultCodeHintProviders.insertHint($hint); + }; + + exports.CodeHintsProvider = CodeHintsProvider; +}); diff --git a/src/extensions/default/PhpTooling/client.js b/src/extensions/default/PhpTooling/client.js new file mode 100644 index 00000000000..74f67d8b7ef --- /dev/null +++ b/src/extensions/default/PhpTooling/client.js @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*global exports */ +/*global process */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + net = require("net"), + cp = require("child_process"), + execa = require("execa"), + semver = require('semver'), + clientName = "PhpClient", + executablePath = "", + memoryLimit = ""; + +function validatePhpExecutable(confParams) { + executablePath = confParams["executablePath"] || + (process.platform === 'win32' ? 'php.exe' : 'php'); + + memoryLimit = confParams["memoryLimit"] || '4095M'; + + return new Promise(function (resolve, reject) { + if (memoryLimit !== '-1' && !/^\d+[KMG]?$/.exec(memoryLimit)) { + reject("PHP_SERVER_MEMORY_LIMIT_INVALID"); + return; + } + + execa.stdout(executablePath, ['--version']).then(function (output) { + var matchStr = output.match(/^PHP ([^\s]+)/m); + if (!matchStr) { + reject("PHP_VERSION_INVALID"); + return; + } + var version = matchStr[1].split('-')[0]; + if (!/^\d+.\d+.\d+$/.test(version)) { + version = version.replace(/(\d+.\d+.\d+)/, '$1-'); + } + if (semver.lt(version, '7.0.0')) { + reject(["PHP_UNSUPPORTED_VERSION", version]); + return; + } + resolve(); + }).catch(function (err) { + if (err.code === 'ENOENT') { + reject("PHP_EXECUTABLE_NOT_FOUND"); + } else { + reject(["PHP_PROCESS_SPAWN_ERROR", err.code]); + console.error(err); + } + return; + }); + }); +} + +var serverOptions = function () { + return new Promise(function (resolve, reject) { + var server = net.createServer(function (socket) { + console.log('PHP process connected'); + socket.on('end', function () { + console.log('PHP process disconnected'); + }); + server.close(); + resolve({ + reader: socket, + writer: socket + }); + }); + server.listen(0, '127.0.0.1', function () { + var pathToPHP = __dirname + "/vendor/felixfbecker/language-server/bin/php-language-server.php"; + var childProcess = cp.spawn(executablePath, [ + pathToPHP, + '--tcp=127.0.0.1:' + server.address().port, + '--memory-limit=' + memoryLimit + ]); + childProcess.stderr.on('data', function (chunk) { + var str = chunk.toString(); + console.log('PHP Language Server:', str); + }); + childProcess.on('exit', function (code, signal) { + console.log( + "Language server exited " + (signal ? "from signal " + signal : "with exit code " + code) + ); + }); + return childProcess; + }); + }); + }, + options = { + serverOptions: serverOptions + }; + + +function init(domainManager) { + var client = new LanguageClient(clientName, domainManager, options); + client.addOnRequestHandler('validatePhpExecutable', validatePhpExecutable); +} + +exports.init = init; diff --git a/src/extensions/default/PhpTooling/composer.json b/src/extensions/default/PhpTooling/composer.json new file mode 100644 index 00000000000..ce39680788b --- /dev/null +++ b/src/extensions/default/PhpTooling/composer.json @@ -0,0 +1,7 @@ +{ + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "felixfbecker/language-server": "^5.4" + } +} diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js new file mode 100755 index 00000000000..4d92211822d --- /dev/null +++ b/src/extensions/default/PhpTooling/main.js @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + ProjectManager = brackets.getModule("project/ProjectManager"), + EditorManager = brackets.getModule("editor/EditorManager"), + LanguageManager = brackets.getModule("language/LanguageManager"), + CodeHintManager = brackets.getModule("editor/CodeHintManager"), + ParameterHintManager = brackets.getModule("features/ParameterHintsManager"), + JumpToDefManager = brackets.getModule("features/JumpToDefManager"), + CodeInspection = brackets.getModule("language/CodeInspection"), + DefaultProviders = brackets.getModule("languageTools/DefaultProviders"), + CodeHintsProvider = require("CodeHintsProvider").CodeHintsProvider, + DefaultEventHandlers = brackets.getModule("languageTools/DefaultEventHandlers"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + Strings = brackets.getModule("strings"), + Dialogs = brackets.getModule("widgets/Dialogs"), + DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"), + Commands = brackets.getModule("command/Commands"), + CommandManager = brackets.getModule("command/CommandManager"), + StringUtils = brackets.getModule("utils/StringUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "PhpClient", + _client = null, + evtHandler, + phpConfig = { + enablePhpTooling: true, + executablePath: "php", + memoryLimit: "4095M", + validateOnType: "false" + }, + DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW = "debug.openPrefsInSplitView", + phpServerRunning = false, + serverCapabilities, + currentRootPath; + + PreferencesManager.definePreference("php", "object", phpConfig, { + description: Strings.DESCRIPTION_PHP_TOOLING_CONFIGURATION + }); + + PreferencesManager.on("change", "php", function () { + var newPhpConfig = PreferencesManager.get("php"); + + if ((newPhpConfig["executablePath"] !== phpConfig["executablePath"]) + || (newPhpConfig["enablePhpTooling"] !== phpConfig["enablePhpTooling"])) { + phpConfig = newPhpConfig; + runPhpServer(); + return; + } + phpConfig = newPhpConfig; + }); + + var handleProjectOpen = function (event, directory) { + if(serverCapabilities["workspace"] && serverCapabilities["workspace"]["workspaceFolders"]) { + _client.notifyProjectRootsChanged({ + foldersAdded: [directory.fullPath], + foldersRemoved: [currentRootPath] + }); + currentRootPath = directory.fullPath; + } else { + _client.restart({ + rootPath: directory.fullPath + }).done(handlePostPhpServerStart); + } + }; + + function registerToolingProviders() { + var chProvider = new CodeHintsProvider(_client), + phProvider = new DefaultProviders.ParameterHintsProvider(_client), + lProvider = new DefaultProviders.LintingProvider(_client), + jdProvider = new DefaultProviders.JumpToDefProvider(_client); + + JumpToDefManager.registerJumpToDefProvider(jdProvider, ["php"], 0); + CodeHintManager.registerHintProvider(chProvider, ["php"], 0); + ParameterHintManager.registerHintProvider(phProvider, ["php"], 0); + CodeInspection.register(["php"], { + name: Strings.PHP_DIAGNOSTICS, + scanFile: lProvider.getInspectionResults.bind(lProvider) + }); + + _client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider)); + } + + function addEventHandlers() { + _client.addOnLogMessage(function () {}); + _client.addOnShowMessage(function () {}); + evtHandler = new DefaultEventHandlers.EventPropagationProvider(_client); + evtHandler.registerClientForEditorEvent(); + + + if (phpConfig["validateOnType"] !== "false") { + _client.addOnDocumentChangeHandler(function () { + CodeInspection.requestRun(Strings.PHP_DIAGNOSTICS); + }); + } + + _client.addOnProjectOpenHandler(handleProjectOpen); + } + + function validatePhpExecutable() { + var result = $.Deferred(); + + _client.sendCustomRequest({ + messageType: "brackets", + type: "validatePhpExecutable", + params: phpConfig + }).done(result.resolve).fail(result.reject); + + return result; + } + + function showErrorPopUp(err) { + if (typeof (err) === "string") { + err = Strings[err]; + } else { + err = StringUtils.format(Strings[err[0]], err[1]); + } + var Buttons = [ + { className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, + text: Strings.CANCEL }, + { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_DOWNLOAD, + text: Strings.OPEN_PREFERENNCES} + ]; + Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_ERROR, + Strings.PHP_SERVER_ERROR_TITLE, + err, + Buttons + ).done(function (id) { + if (id === Dialogs.DIALOG_BTN_DOWNLOAD) { + if (CommandManager.get(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW)) { + CommandManager.execute(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW); + } else { + CommandManager.execute(Commands.CMD_OPEN_PREFERENCES); + } + } + }); + } + + function handlePostPhpServerStart() { + if (!phpServerRunning) { + phpServerRunning = true; + registerToolingProviders(); + addEventHandlers(); + EditorManager.off("activeEditorChange.php"); + LanguageManager.off("languageModified.php"); + } + + evtHandler.handleActiveEditorChange(null, EditorManager.getActiveEditor()); + currentRootPath = ProjectManager.getProjectRoot()._path; + setTimeout(function () { + CodeInspection.requestRun(Strings.PHP_DIAGNOSTICS); + }, 1500); + } + + function runPhpServer() { + if (_client && phpConfig["enablePhpTooling"]) { + validatePhpExecutable() + .done(function () { + var startFunc = _client.start.bind(_client); + if (phpServerRunning) { + startFunc = _client.restart.bind(_client); + } + currentRootPath = ProjectManager.getProjectRoot()._path; + startFunc({ + rootPath: currentRootPath + }).done(function (result) { + console.log("php Language Server started"); + serverCapabilities = result.capabilities; + handlePostPhpServerStart(); + }); + }).fail(showErrorPopUp); + } + } + + function activeEditorChangeHandler(event, current) { + if (current) { + var language = current.document.getLanguage(); + if (language.getId() === "php") { + runPhpServer(); + EditorManager.off("activeEditorChange.php"); + LanguageManager.off("languageModified.php"); + } + } + } + + function languageModifiedHandler(event, language) { + if (language && language.getId() === "php") { + runPhpServer(); + EditorManager.off("activeEditorChange.php"); + LanguageManager.off("languageModified.php"); + } + } + + AppInit.appReady(function () { + LanguageTools.initiateToolingService(clientName, clientFilePath, ['php']).done(function (client) { + _client = client; + EditorManager.on("activeEditorChange.php", activeEditorChangeHandler); + LanguageManager.on("languageModified.php", languageModifiedHandler); + activeEditorChangeHandler(null, EditorManager.getActiveEditor()); + }); + }); + + //Only for Unit testing + exports.getClient = function() { return _client; }; +}); diff --git a/src/extensions/default/PhpTooling/package.json b/src/extensions/default/PhpTooling/package.json new file mode 100644 index 00000000000..e9ef5096e72 --- /dev/null +++ b/src/extensions/default/PhpTooling/package.json @@ -0,0 +1,14 @@ +{ + "name": "php-Tooling", + "version": "1.0.0", + "description": "Advanced Tooling support for PHP", + "author": "niteskum", + "main": "main.js", + "scripts": { + "postinstall": "composer require felixfbecker/language-server && composer run-script --working-dir=vendor/felixfbecker/language-server parse-stubs" + }, + "dependencies": { + "execa": "1.0.0", + "semver": "5.6.0" + } +} diff --git a/src/extensions/default/PhpTooling/phpGlobals.json b/src/extensions/default/PhpTooling/phpGlobals.json new file mode 100644 index 00000000000..cbb8bb501fe --- /dev/null +++ b/src/extensions/default/PhpTooling/phpGlobals.json @@ -0,0 +1,75 @@ +{ + "$GLOBALS": { + "description": "An associative array containing references to all variables which are currently defined in the global scope of the script. The variable names are the keys of the array.", + "type": "array" + }, + "$_SERVER": { + "description": "$_SERVER is an array containing information such as headers, paths, and script locations. The entries in this array are created by the web server. There is no guarantee that every web server will provide any of these; servers may omit some, or provide others not listed here. That said, a large number of these variables are accounted for in the CGI/1.1 specification, so you should be able to expect those.", + "type": "array" + }, + "$_GET": { + "description": "An associative array of variables passed to the current script via the URL parameters.", + "type": "array" + }, + "$_POST": { + "description": "An associative array of variables passed to the current script via the HTTP POST method.", + "type": "array" + }, + "$_FILES": { + "description": "An associative array of items uploaded to the current script via the HTTP POST method.", + "type": "array" + }, + "$_REQUEST": { + "description": "An associative array that by default contains the contents of $_GET, $_POST and $_COOKIE.", + "type": "array" + }, + "$_SESSION": { + "description": "An associative array containing session variables available to the current script. See the Session functions documentation for more information on how this is used.", + "type": "array" + }, + "$_ENV": { + "description": "An associative array of variables passed to the current script via the environment method. \r\n\r\nThese variables are imported into PHP\"s global namespace from the environment under which the PHP parser is running. Many are provided by the shell under which PHP is running and different systems are likely running different kinds of shells, a definitive list is impossible. Please see your shell\"s documentation for a list of defined environment variables. \r\n\r\nOther environment variables include the CGI variables, placed there regardless of whether PHP is running as a server module or CGI processor.", + "type": "array" + }, + "$_COOKIE": { + "description": "An associative array of variables passed to the current script via HTTP Cookies.", + "type": "array" + }, + "$php_errormsg": { + "description": "$php_errormsg is a variable containing the text of the last error message generated by PHP. This variable will only be available within the scope in which the error occurred, and only if the track_errors configuration option is turned on (it defaults to off).", + "type": "array" + }, + "$HTTP_RAW_POST_DATA": { + "description": "$HTTP_RAW_POST_DATA contains the raw POST data. See always_populate_raw_post_data", + "type": "array" + }, + "$http_response_header": { + "description": "The $http_response_header array is similar to the get_headers() function. When using the HTTP wrapper, $http_response_header will be populated with the HTTP response headers. $http_response_header will be created in the local scope.", + "type": "array" + }, + "$argc": { + "description": "Contains the number of arguments passed to the current script when running from the command line.", + "type": "array" + }, + "$argv": { + "description": "Contains an array of all the arguments passed to the script when running from the command line.", + "type": "array" + }, + "$this": { + "description": "Refers to the current object", + "type": "array" + }, + "parent": { + "description": "", + "type": "" + }, + "self": { + "description": "", + "type": "" + }, + "_destruct": { + "description": "", + "type": "" + } + +} \ No newline at end of file diff --git a/src/extensions/default/PhpTooling/unittest-files/mac/invalidphp b/src/extensions/default/PhpTooling/unittest-files/mac/invalidphp new file mode 100755 index 0000000000000000000000000000000000000000..487eecd1068df6db2a811eb09995043695cb53e8 GIT binary patch literal 10292 zcmeHNe{2)i9e<7k%UFg3r5)`U7)mM!st_jxl8$Zh$Zh9gLm;6=6#C*gN9@h9GyB{+ z+QQP3sJ)p;nW}b^s5GYik}7RWwQfSy6s4nDBJD>5n&_lfg-UCBDWzhQ77#0VpYOY~ zeYvC)DgXM(?|py0_j%vt$azXlccls{J6`2y4ZOo<07H@673*hD3l8WZjI&{wCF`nx0?MlRWpPF2Nx7PG8?-t2+ zyw&->Ksk?v{vV#l#LLHxWGfzxcZ~(Fc%26Pd-q7K{`@uOw#5xHrR^04B^-0<2*oB` z{&mE;8KWu{pq-%k6mjF4!UQ(m&luICxuh6oAm0tzQO(#TC_e+f4cVHgF6FzdN*h15 znK3{uNLbe7601bsmQE$N#iCu?y8C0@kSTp0nf&>K@uw{ETV*20Nsm+-5}Qon51 zg>rlS_WqQr^wxgWY(l8)0pu#=2nJ2G#*`_ZrBsSQDFUSkEF}U5W$j(x!|n2k531zs zm)7GpCXY0J3r|q5Yy*rwf$!AL)&?ffbSj|D=sT&`Y@-{4B9*>T$i}GL1V*EJ zMtVaSx5MZfm)`JcLz@Xr&JIij9k+Z>zB>lVcjBfypxyAhS}(M^#({n-p#3YTUD8ic z)!~Ws=SU5v@+cNzMZW%NT{X8++KM)?zqrM})qrS&{{ODP~i07343apn` z!x=qJ9E1LHrC}V?e+U)L$gc>32A{#SUDQuP)PGFXe~DP@&yf|=&@AYrUJdHn(8|~% z@qOK|{hi`F{HAN@EW8hB*X5e40Zkh7XXUk$_ljKeYQVK`HmJ?`YvzK(bH}TxW9?!< z`*%PaI1|*~leE9&+BzleouKwnAUkj-(0n!ho#615;{n$r6RsL*LY|bS0Zq%~93n6g z&_2|E19?uGaL6O+S7n!!o;D)ZDUjdKO&cSg5NkBp{9$@au;#tsaPIh;eD5=u0CXE@ zzMftwXQeTDvW0HX>~fj+&4tl5mf(c`Wz?Y1tQT}rr{!E+e{DKFDCvF_qq*vPu(EU2 zYejjVDA$Q{y(rzH+$74)qI^J<9#L))<%6QE6=j_$>ro=eBVTw43(VJ^tEOvVG;$A= zvopC|F0z}rw}_*M^4asmogr?DI45RucAU5t;$9~1d&Ip++-t<0CT=-UXP+l-6LBYr z3laAeaYMvqhoj#IxQ9+5A`;ng}fg^7vhSK+u>9L6} z0mri_s*pv3S4g>2KYwy?kU3v)+y{xCsMf&X*bjzk-b+FV%x2_|kR8J)jvx!mJ75TX zHC2OWLRdkNz6Y8&d@o^UK$|u5WhN*LioM zc&Gut`WiQ$Dj8dcOw?^siV5b2ojbH_e-(VC=Tlm#G#m6EloH9Lr~iC|80itvOK`aY ze_WifRkCv65(+CDLs115Ym&NQ8(A66!)>Q>U{k}4`TzAPlb$r`^CmrQ(ictovPs8H zI%U%HCVk7KGbX)i(irY5hz9A zV0)4X1=y#9N@s3y` z9EvH;>6F5cg~Ey|9oXAm$BhnIvT0uBrf4F?yFw{tM~|YSq4YQbwpa(^t%p4thBNfW z+aKQB&b{7nB$VW8G89!)7>op6b0|g~tBE8jq#YbDv!kli8zw;c&H-D!hHq{2pc)L{ z5^zol^)Bw5)X;~S5Plq5dgI1pv2-e8^xe7i<~veKTut`x_DToZ8#u)x@`*w6A?ULr znA4rNWvsTs>^5LUrW6%3-Q5*cc?h2xgcI>p6bp+d6*ZDfJYkG*(6MOy#&AptC8ckY z|IZjz+SO)NN%ltLAypBJW>n~TI1}PsN>4P-u^7S;YVo6@MWLQeJr=Zs zh7wMz3cmRW(Q4n=8;ZtRH66P-*>wJ0_w-$3j!R{y#bF#Q;#AJ%Fb*N diff --git a/src/extensions/default/PhpTooling/unittest-files/test/test3.php b/src/extensions/default/PhpTooling/unittest-files/test/test3.php new file mode 100644 index 00000000000..70981b54d1f --- /dev/null +++ b/src/extensions/default/PhpTooling/unittest-files/test/test3.php @@ -0,0 +1,10 @@ +Y!bA+ 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�?tQJk0xc0qi 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 literal 0 HcmV?d00001 diff --git a/src/extensions/default/PhpTooling/unittests.js b/src/extensions/default/PhpTooling/unittests.js new file mode 100644 index 00000000000..722acc5b993 --- /dev/null +++ b/src/extensions/default/PhpTooling/unittests.js @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ +/*global describe, runs, beforeEach, it, expect, waitsFor, waitsForDone, beforeFirst, afterLast */ +define(function (require, exports, module) { + 'use strict'; + + var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), + Strings = brackets.getModule("strings"), + FileUtils = brackets.getModule("file/FileUtils"), + StringUtils = brackets.getModule("utils/StringUtils"); + + var extensionRequire, + phpToolingExtension, + testWindow, + $, + PreferencesManager, + CodeInspection, + DefaultProviders, + CodeHintsProvider, + EditorManager, + testEditor, + testFolder = FileUtils.getNativeModuleDirectoryPath(module) + "/unittest-files/", + testFile1 = "test1.php", + testFile2 = "test2.php"; + + describe("PhpTooling", function () { + + beforeFirst(function () { + + // Create a new window that will be shared by ALL tests in this spec. + SpecRunnerUtils.createTestWindowAndRun(this, function (w) { + testWindow = w; + $ = testWindow.$; + var brackets = testWindow.brackets; + extensionRequire = brackets.test.ExtensionLoader.getRequireContextForExtension("PhpTooling"); + phpToolingExtension = extensionRequire("main"); + }); + }); + + afterLast(function () { + waitsForDone(phpToolingExtension.getClient().stop(), "stoping php server"); + SpecRunnerUtils.closeTestWindow(); + testWindow = null; + }); + + + beforeEach(function () { + EditorManager = testWindow.brackets.test.EditorManager; + PreferencesManager = testWindow.brackets.test.PreferencesManager; + CodeInspection = testWindow.brackets.test.CodeInspection; + CodeInspection.toggleEnabled(true); + DefaultProviders = testWindow.brackets.getModule("languageTools/DefaultProviders"); + CodeHintsProvider = extensionRequire("CodeHintsProvider"); + }); + + /** + * Does a busy wait for a given number of milliseconds + * @param {Number} milliSeconds - number of milliSeconds to wait + */ + function waitForMilliSeconds(milliSeconds) { + var flag = false; + + setTimeout(function () { + flag = true; + }, milliSeconds); + + waitsFor(function () { + return flag; + }, "This should not fail. Please check the timeout values.", + milliSeconds + 10); // We give 10 milliSeconds as grace period + } + + /** + * Check the presence of a Button in Error Prompt + * @param {String} btnId - "CANCEL" or "OPEN" + */ + function checkPopUpButton(clickbtnId) { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + btn = errorPopUp.find('.dialog-button'); + + // Test if the update bar button has been displayed. + expect(btn.length).toBe(2); + if (clickbtnId) { + clickButton(clickbtnId); + } + } + + /** + * Check the presence of a Button in Error Prompt + * @param {String} btnId - Button OPEN or Cancel Button + */ + function clickButton(btnId) { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + btn = errorPopUp.find('.dialog-button'), + openBtn, + cancelBtn, + clickBtn; + if (btn[0].classList.contains("primary")) { + openBtn = btn[0]; + cancelBtn = btn[1]; + } + + if (btn[1].classList.contains("primary")) { + openBtn = btn[1]; + cancelBtn = btn[0]; + } + clickBtn = cancelBtn; + + if(btnId === "OPEN") { + clickBtn = openBtn; + } + + if(clickBtn) { + clickBtn.click(); + waitForMilliSeconds(3000); + runs(function() { + expect(doc.find(".error-dialog.instance").length).toBe(0); + }); + } + } + + /** + * Check the presence of Error Prompt String on Brackets Window + * @param {String} title - Title String Which will be matched with Update Bar heading. + * @param {String} description - description String Which will be matched with Update Bar description. + */ + function checkPopUpString(title, titleDescription) { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + heading = errorPopUp.find('.dialog-title'), + description = errorPopUp.find('.dialog-message'); + + // Test if the update bar has been displayed. + //expect(errorPopUp.length).toBe(1); + if (title) { + expect(heading.text()).toBe(title); + } + if (titleDescription) { + expect(description.text()).toBe(titleDescription); + } + } + + function toggleDiagnosisResults(visible) { + var doc = $(testWindow.document), + problemsPanel = doc.find("#problems-panel"), + statusInspection = $("#status-inspection"); + statusInspection.triggerHandler("click"); + expect(problemsPanel.is(":visible")).toBe(visible); + } + + /** + * Wait for the editor to change positions, such as after a jump to + * definition has been triggered. Will timeout after 3 seconds + * + * @param {{line:number, ch:number}} oldLocation - the original line/col + * @param {Function} callback - the callback to apply once the editor has changed position + */ + function _waitForJump(jumpPromise, callback) { + var cursor = null, + complete = false; + + jumpPromise.done(function () { + complete = true; + }); + + waitsFor(function () { + var activeEditor = EditorManager.getActiveEditor(); + cursor = activeEditor.getCursorPos(); + return complete; + }, "Expected jump did not occur", 3000); + + runs(function () { callback(cursor); }); + } + + /* + * Expect a given list of hints to be present in a given hint + * response object + * + * @param {Object + jQuery.Deferred} hintObj - a hint response object, + * possibly deferred + * @param {Array.} expectedHints - a list of hints that should be + * present in the hint response + */ + function expecthintsPresent(expectedHints) { + var hintObj = (new CodeHintsProvider.CodeHintsProvider(phpToolingExtension.getClient())).getHints(null); + _waitForHints(hintObj, function (hintList) { + expect(hintList).toBeTruthy(); + expectedHints.forEach(function (expectedHint) { + expect(_indexOf(hintList, expectedHint)).not.toBe(-1); + }); + }); + } + + /* + * Return the index at which hint occurs in hintList + * + * @param {Array.} hintList - the list of hints + * @param {string} hint - the hint to search for + * @return {number} - the index into hintList at which the hint occurs, + * or -1 if it does not + */ + function _indexOf(hintList, hint) { + var index = -1, + counter = 0; + + for (counter; counter < hintList.length; counter++) { + if (hintList[counter].data("token").label === hint) { + index = counter; + break; + } + } + return index; + } + + /* + * Wait for a hint response object to resolve, then apply a callback + * to the result + * + * @param {Object + jQuery.Deferred} hintObj - a hint response object, + * possibly deferred + * @param {Function} callback - the callback to apply to the resolved + * hint response object + */ + function _waitForHints(hintObj, callback) { + var complete = false, + hintList = null; + + if (hintObj.hasOwnProperty("hints")) { + complete = true; + hintList = hintObj.hints; + } else { + hintObj.done(function (obj) { + complete = true; + hintList = obj.hints; + }); + } + + waitsFor(function () { + return complete; + }, "Expected hints did not resolve", 3000); + + runs(function () { callback(hintList); }); + } + + /** + * Show a function hint based on the code at the cursor. Verify the + * hint matches the passed in value. + * + * @param {Array<{name: string, type: string, isOptional: boolean}>} + * expectedParams - array of records, where each element of the array + * describes a function parameter. If null, then no hint is expected. + * @param {number} expectedParameter - the parameter at cursor. + */ + function expectParameterHint(expectedParams, expectedParameter) { + var requestStatus = null; + var request, + complete = false; + runs(function () { + request = (new DefaultProviders.ParameterHintsProvider(phpToolingExtension.getClient())) + .getParameterHints(); + request.done(function (status) { + complete = true; + requestStatus = status; + }).fail(function(){ + complete = true; + }); + }); + + waitsFor(function () { + return complete; + }, "Expected Parameter hints did not resolve", 3000); + + if (expectedParams === null) { + expect(requestStatus).toBe(null); + return; + } + + function expectHint(hint) { + var params = hint.parameters, + n = params.length, + i; + + // compare params to expected params + expect(params.length).toBe(expectedParams.length); + expect(hint.currentIndex).toBe(expectedParameter); + + for (i = 0; i < n; i++) { + expect(params[i].label).toBe(expectedParams[i]); + } + + } + runs(function() { + expectHint(requestStatus); + }); + } + + /** + * Trigger a jump to definition, and verify that the editor jumped to + * the expected location. The new location is the variable definition + * or function definition of the variable or function at the current + * cursor location. Jumping to the new location will cause a new editor + * to be opened or open an existing editor. + * + * @param {{line:number, ch:number, file:string}} expectedLocation - the + * line, column, and optionally the new file the editor should jump to. If the + * editor is expected to stay in the same file, then file may be omitted. + */ + function editorJumped(expectedLocation) { + var jumpPromise = (new DefaultProviders.JumpToDefProvider(phpToolingExtension.getClient())).doJumpToDef(); + + _waitForJump(jumpPromise, function (newCursor) { + expect(newCursor.line).toBe(expectedLocation.line); + expect(newCursor.ch).toBe(expectedLocation.ch); + if (expectedLocation.file) { + var activeEditor = EditorManager.getActiveEditor(); + expect(activeEditor.document.file.name).toBe(expectedLocation.file); + } + }); + + } + + /** + * Check the presence of Error Prompt on Brackets Window + */ + function checkErrorPopUp() { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + errorPopUpHeader = errorPopUp.find(".modal-header"), + errorPopUpBody = errorPopUp.find(".modal-body"), + errorPopUpFooter = errorPopUp.find(".modal-footer"), + errorPopUpPresent = false; + + runs(function () { + expect(errorPopUp.length).toBe(1); + expect(errorPopUpHeader).not.toBeNull(); + expect(errorPopUpBody).not.toBeNull(); + expect(errorPopUpFooter).not.toBeNull(); + }); + + if (errorPopUp && errorPopUp.length > 0) { + errorPopUpPresent = true; + } + return errorPopUpPresent; + } + + it("phpTooling Exiension should be loaded Successfully", function () { + waitForMilliSeconds(5000); + runs(function () { + expect(phpToolingExtension).not.toBeNull(); + }); + }); + + it("should attempt to start php server and fail due to lower version of php", function () { + var phpExecutable = testWindow.brackets.platform === "mac" ? "/mac/invalidphp" : "/win/invalidphp"; + PreferencesManager.set("php", { + "executablePath": testFolder + phpExecutable + }, { + locations: {scope: "session"} + }); + waitForMilliSeconds(5000); + runs(function () { + checkErrorPopUp(); + checkPopUpString(Strings.PHP_SERVER_ERROR_TITLE, + StringUtils.format(Strings.PHP_UNSUPPORTED_VERSION, "5.6.30")); + checkPopUpButton("CANCEL"); + }); + }); + + it("should attempt to start php server and fail due to invaild executable", function () { + PreferencesManager.set("php", {"executablePath": "/invalidPath/php"}, {locations: {scope: "session"}}); + waitForMilliSeconds(5000); + runs(function () { + checkErrorPopUp(); + checkPopUpString(Strings.PHP_SERVER_ERROR_TITLE, Strings.PHP_EXECUTABLE_NOT_FOUND); + checkPopUpButton("CANCEL"); + }); + }); + + it("should attempt to start php server and fail due to invaild memory limit in prefs settings", function () { + PreferencesManager.set("php", {"memoryLimit": "invalidValue"}, {locations: {scope: "session"}}); + waitForMilliSeconds(5000); + runs(function () { + checkErrorPopUp(); + checkPopUpString(Strings.PHP_SERVER_ERROR_TITLE, Strings.PHP_SERVER_MEMORY_LIMIT_INVALID); + checkPopUpButton("CANCEL"); + }); + + runs(function () { + SpecRunnerUtils.loadProjectInTestWindow(testFolder + "test"); + }); + }); + + it("should attempt to start php server and success", function () { + PreferencesManager.set("php", {"memoryLimit": "4095M"}, {locations: {scope: "session"}}); + + waitsForDone(SpecRunnerUtils.openProjectFiles([testFile1]), "open test file: " + testFile1); + waitForMilliSeconds(5000); + runs(function () { + toggleDiagnosisResults(false); + toggleDiagnosisResults(true); + }); + }); + + it("should filter hints by query", function () { + waitsForDone(SpecRunnerUtils.openProjectFiles([testFile2]), "open test file: " + testFile2); + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 15, ch: 3 }); + expecthintsPresent(["$A11", "$A12", "$A13"]); + }); + }); + + it("should show inbuilt functions in hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 17, ch: 2 }); + expecthintsPresent(["fopen", "for", "foreach"]); + }); + }); + + it("should show static global variables in hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 20, ch: 1 }); + expecthintsPresent(["$_COOKIE", "$_ENV"]); + }); + }); + + it("should not show parameter hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 25, ch: 5 }); + expectParameterHint(null); + }); + }); + + it("should show no parameter as a hint", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 27, ch: 19 }); + expectParameterHint([], 0); + }); + }); + + it("should show parameters hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 26, ch: 9 }); + expectParameterHint([ + "string $filename", + "string $mode", + "bool $use_include_path = null", + "resource $context = null"], 1); + }); + }); + + it("should jump to earlier defined variable", function () { + var start = { line: 4, ch: 2 }; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + editorJumped({line: 2, ch: 0}); + }); + }); + + it("should jump to class declared in other module file", function () { + var start = { line: 9, ch: 11 }; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + editorJumped({line: 4, ch: 0, file: "test3.php"}); + }); + }); + }); +}); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 074c462c438..088b6e02489 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -872,11 +872,22 @@ define({ "DESCRIPTION_AUTO_UPDATE" : "Enable/disable Brackets Auto-update", "AUTOUPDATE_ERROR" : "Error!", "AUTOUPDATE_IN_PROGRESS" : "An update is already in progress.", - + "NUMBER_WITH_PERCENTAGE" : "{0}%", // Strings for Related Files "CMD_FIND_RELATED_FILES" : "Find Related Files", - + + ///String for Php Tooling Extensions + "PHP_VERSION_INVALID" : "Error parsing PHP version. Please check the output of the “php –version” command.", + "PHP_UNSUPPORTED_VERSION" : "Install PHP7 runtime for enabling PHP-related tooling such as Code Hints, Parameter Hints, Jump To Definition and more. Version found: {0}", + "PHP_EXECUTABLE_NOT_FOUND" : "PHP runtime not found. Install PHP7 runtime and set the path to system PATH or executablePath in php Preferences appropriately.", + "PHP_PROCESS_SPAWN_ERROR" : "Error code {0} encountered while starting the PHP process.", + "PHP_SERVER_ERROR_TITLE" : "Error", + "PHP_SERVER_MEMORY_LIMIT_INVALID" : "The memory limit you provided is invalid. Review the PHP preferences to set the correct value.", + "DESCRIPTION_PHP_TOOLING_CONFIGURATION" : "PHP Tooling default configuration settings", + "OPEN_PREFERENNCES" : "Open Preferences", + "PHP_DIAGNOSTICS" : "Diagnostics", + //Strings for LanguageTools Preferences LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" }); From c5f1960b0059790b60399a63bbcb17dfd1393c99 Mon Sep 17 00:00:00 2001 From: niteskum Date: Fri, 5 Apr 2019 11:49:22 +0530 Subject: [PATCH 057/149] switching from anonymous to pseudonymous to align with the definitions used in the GDPR --- src/nls/root/strings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 088b6e02489..720909da661 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -650,10 +650,10 @@ define({ // extensions/default/HealthData "HEALTH_DATA_NOTIFICATION" : "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE" : "{APP_NAME} Health Report", - "HEALTH_DATA_DO_TRACK" : "Share anonymous information on how I use {APP_NAME}", - "HEALTH_DATA_NOTIFICATION_MESSAGE" : "In order to improve {APP_NAME}, we periodically send limited, anonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

You can see your data or choose not to share data by selecting Help > Health Report.

Learn more about {APP_NAME} Health Report", + "HEALTH_DATA_DO_TRACK" : "Share pseudonymous information on how I use {APP_NAME}", + "HEALTH_DATA_NOTIFICATION_MESSAGE" : "In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

You can see your data or choose not to share data by selecting Help > Health Report.

Learn more about {APP_NAME} Health Report", "HEALTH_DATA_PREVIEW" : "{APP_NAME} Health Report", - "HEALTH_DATA_PREVIEW_INTRO" : "

In order to improve {APP_NAME}, we periodically send limited, anonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

Below is a preview of the data that will be sent in your next Health Report if it is enabled.

", + "HEALTH_DATA_PREVIEW_INTRO" : "

In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

Below is a preview of the data that will be sent in your next Health Report if it is enabled.

", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME" : "Time", From 8b8e3382d1178859414243464402461685998bd8 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Fri, 5 Apr 2019 15:25:16 +0530 Subject: [PATCH 058/149] Removing the old Brackets CLA check (#14684) * Commenting the test script command * Adding the new command * Removing the old command --- package.json | 2 +- src/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ddc92dd54af..39a007375b3 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "scripts": { "prepush": "npm run eslint", "postinstall": "grunt install", - "test": "grunt test cla-check-pull", + "test": "grunt test", "eslint": "grunt eslint" }, "licenses": [ diff --git a/src/config.json b/src/config.json index ef4e55d96b2..016a404abb7 100644 --- a/src/config.json +++ b/src/config.json @@ -90,7 +90,7 @@ "scripts": { "prepush": "npm run eslint", "postinstall": "grunt install", - "test": "grunt test cla-check-pull", + "test": "grunt test", "eslint": "grunt eslint" }, "licenses": [ From d602c87cab2087a532d472d4cf9b29471cc20e43 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Fri, 5 Apr 2019 15:31:22 +0530 Subject: [PATCH 059/149] Fixed LSP Linting Synchronization Issues (#14685) * Fixed LSP Linting Synchrnization Issues * Addressed review comments * Removing Linter Name as it is not required --- src/extensions/default/PhpTooling/main.js | 30 ++++++++++++----------- src/languageTools/DefaultProviders.js | 25 +++++++++++++++++++ src/nls/root/strings.js | 1 - 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js index 4d92211822d..d2ecc966bb1 100755 --- a/src/extensions/default/PhpTooling/main.js +++ b/src/extensions/default/PhpTooling/main.js @@ -57,7 +57,11 @@ define(function (require, exports, module) { DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW = "debug.openPrefsInSplitView", phpServerRunning = false, serverCapabilities, - currentRootPath; + currentRootPath, + chProvider, + phProvider, + lProvider, + jdProvider; PreferencesManager.definePreference("php", "object", phpConfig, { description: Strings.DESCRIPTION_PHP_TOOLING_CONFIGURATION @@ -66,6 +70,9 @@ define(function (require, exports, module) { PreferencesManager.on("change", "php", function () { var newPhpConfig = PreferencesManager.get("php"); + if (lProvider && newPhpConfig["validateOnType"] !== phpConfig["validateOnType"]) { + lProvider._validateOnType = !(newPhpConfig["validateOnType"] === "false"); + } if ((newPhpConfig["executablePath"] !== phpConfig["executablePath"]) || (newPhpConfig["enablePhpTooling"] !== phpConfig["enablePhpTooling"])) { phpConfig = newPhpConfig; @@ -76,6 +83,7 @@ define(function (require, exports, module) { }); var handleProjectOpen = function (event, directory) { + lProvider.clearExistingResults(); if(serverCapabilities["workspace"] && serverCapabilities["workspace"]["workspaceFolders"]) { _client.notifyProjectRootsChanged({ foldersAdded: [directory.fullPath], @@ -90,17 +98,17 @@ define(function (require, exports, module) { }; function registerToolingProviders() { - var chProvider = new CodeHintsProvider(_client), - phProvider = new DefaultProviders.ParameterHintsProvider(_client), - lProvider = new DefaultProviders.LintingProvider(_client), - jdProvider = new DefaultProviders.JumpToDefProvider(_client); + chProvider = new CodeHintsProvider(_client), + phProvider = new DefaultProviders.ParameterHintsProvider(_client), + lProvider = new DefaultProviders.LintingProvider(_client), + jdProvider = new DefaultProviders.JumpToDefProvider(_client); JumpToDefManager.registerJumpToDefProvider(jdProvider, ["php"], 0); CodeHintManager.registerHintProvider(chProvider, ["php"], 0); ParameterHintManager.registerHintProvider(phProvider, ["php"], 0); CodeInspection.register(["php"], { - name: Strings.PHP_DIAGNOSTICS, - scanFile: lProvider.getInspectionResults.bind(lProvider) + name: "", + scanFileAsync: lProvider.getInspectionResultsAsync.bind(lProvider) }); _client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider)); @@ -114,9 +122,7 @@ define(function (require, exports, module) { if (phpConfig["validateOnType"] !== "false") { - _client.addOnDocumentChangeHandler(function () { - CodeInspection.requestRun(Strings.PHP_DIAGNOSTICS); - }); + lProvider._validateOnType = true; } _client.addOnProjectOpenHandler(handleProjectOpen); @@ -170,12 +176,8 @@ define(function (require, exports, module) { EditorManager.off("activeEditorChange.php"); LanguageManager.off("languageModified.php"); } - evtHandler.handleActiveEditorChange(null, EditorManager.getActiveEditor()); currentRootPath = ProjectManager.getProjectRoot()._path; - setTimeout(function () { - CodeInspection.requestRun(Strings.PHP_DIAGNOSTICS); - }, 1500); } function runPhpServer() { diff --git a/src/languageTools/DefaultProviders.js b/src/languageTools/DefaultProviders.js index d5cc5ad8a9b..6339ace3277 100644 --- a/src/languageTools/DefaultProviders.js +++ b/src/languageTools/DefaultProviders.js @@ -335,6 +335,8 @@ define(function (require, exports, module) { function LintingProvider() { this._results = new Map(); + this._promiseMap = new Map(); + this._validateOnType = false; } LintingProvider.prototype.clearExistingResults = function (filePath) { @@ -342,9 +344,11 @@ define(function (require, exports, module) { if (filePathProvided) { this._results.delete(filePath); + this._promiseMap.delete(filePath); } else { //clear all results this._results.clear(); + this._promiseMap.clear(); } }; @@ -371,6 +375,27 @@ define(function (require, exports, module) { this._results.set(filePath, { errors: errors }); + if(this._promiseMap.get(filePath)) { + this._promiseMap.get(filePath).resolve(this._results.get(filePath)); + this._promiseMap.delete(filePath); + } + if (this._validateOnType) { + var editor = EditorManager.getActiveEditor(), + docPath = editor ? editor.document.file._path : ""; + if (filePath === docPath) { + CodeInspection.requestRun(); + } + } + }; + + LintingProvider.prototype.getInspectionResultsAsync = function (fileText, filePath) { + var result = $.Deferred(); + + if (this._results.get(filePath)) { + return result.resolve(this._results.get(filePath)); + } + this._promiseMap.set(filePath, result); + return result; }; LintingProvider.prototype.getInspectionResults = function (fileText, filePath) { diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 720909da661..f9f8d92de3b 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -886,7 +886,6 @@ define({ "PHP_SERVER_MEMORY_LIMIT_INVALID" : "The memory limit you provided is invalid. Review the PHP preferences to set the correct value.", "DESCRIPTION_PHP_TOOLING_CONFIGURATION" : "PHP Tooling default configuration settings", "OPEN_PREFERENNCES" : "Open Preferences", - "PHP_DIAGNOSTICS" : "Diagnostics", //Strings for LanguageTools Preferences LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" From dcb9186909570bc95f01f8d732d7b3de14a9b7e0 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Mon, 8 Apr 2019 15:32:38 +0530 Subject: [PATCH 060/149] Php Tooling Strings changes (#14688) * Php Tooling Strings changes * Addressed review comments --- src/nls/root/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index f9f8d92de3b..c66291168a0 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -880,7 +880,7 @@ define({ ///String for Php Tooling Extensions "PHP_VERSION_INVALID" : "Error parsing PHP version. Please check the output of the “php –version” command.", "PHP_UNSUPPORTED_VERSION" : "Install PHP7 runtime for enabling PHP-related tooling such as Code Hints, Parameter Hints, Jump To Definition and more. Version found: {0}", - "PHP_EXECUTABLE_NOT_FOUND" : "PHP runtime not found. Install PHP7 runtime and set the path to system PATH or executablePath in php Preferences appropriately.", + "PHP_EXECUTABLE_NOT_FOUND" : "PHP runtime not found. Install the PHP7 runtime and update “executablePath” in PHP Preferences appropriately. This enables PHP-related tooling such as Code Hints, Parameter Hints, Jump To Definition and more.", "PHP_PROCESS_SPAWN_ERROR" : "Error code {0} encountered while starting the PHP process.", "PHP_SERVER_ERROR_TITLE" : "Error", "PHP_SERVER_MEMORY_LIMIT_INVALID" : "The memory limit you provided is invalid. Review the PHP preferences to set the correct value.", From a408323d4a4857022dcb37c2c9fe75fdc09e740f Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Tue, 9 Apr 2019 23:42:12 +0530 Subject: [PATCH 061/149] Fix for "GetItNow" action behaviour (#14691) * Fix for "GetItNow" action behaviour Fix for "GetItNow" action behaviour * Change variable success to initSuccess Make the variable name more explicit to depict it's to track update process initialisation success and not the success status of update process itself. --- src/extensions/default/AutoUpdate/main.js | 3 +++ src/utils/UpdateNotification.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/AutoUpdate/main.js b/src/extensions/default/AutoUpdate/main.js index 0f0d4f41bf7..5fd13a0d0c4 100644 --- a/src/extensions/default/AutoUpdate/main.js +++ b/src/extensions/default/AutoUpdate/main.js @@ -483,6 +483,9 @@ define(function (require, exports, module) { //Initiate the auto update, with update params initiateAutoUpdate(updateParams); + + //Send a truthy value to ensure caller is informed about successful initialization of auto-update + return true; } diff --git a/src/utils/UpdateNotification.js b/src/utils/UpdateNotification.js index cdd05f6067e..ed0ec018a00 100644 --- a/src/utils/UpdateNotification.js +++ b/src/utils/UpdateNotification.js @@ -522,8 +522,8 @@ define(function (require, exports, module) { */ function handleUpdateProcess(updates) { var handler = _updateProcessHandler || _defaultUpdateProcessHandler; - var success = handler(updates); - if (_updateProcessHandler && !success) { + var initSuccess = handler(updates); + if (_updateProcessHandler && !initSuccess) { // Give a chance to default handler in case // the auot update mechanism has failed. _defaultUpdateProcessHandler(updates); From 5658e3d3807645177ac3312ec6741b43116a2b83 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Wed, 10 Apr 2019 00:05:39 +0530 Subject: [PATCH 062/149] Sending Analytics Data for file Operation and LSP features (#14683) * Sending Analytics Data for file Operation and LSP features * Addressed review comments * Addressed review comments * Addressed review comments --- src/document/DocumentCommandHandlers.js | 2 + src/editor/EditorStatusBar.js | 12 +++ src/languageTools/LanguageClientWrapper.js | 47 +++++++++- src/utils/HealthLogger.js | 102 ++++++++++++++++++++- 4 files changed, 159 insertions(+), 4 deletions(-) diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index 28ea9a9dfb0..832148befb4 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -788,6 +788,7 @@ define(function (require, exports, module) { .done(function () { docToSave.notifySaved(); result.resolve(file); + HealthLogger.fileSaved(docToSave); }) .fail(function (err) { if (err === FileSystemError.CONTENTS_MODIFIED) { @@ -1186,6 +1187,7 @@ define(function (require, exports, module) { function doClose(file) { if (!promptOnly) { MainViewManager._close(paneId, file); + HealthLogger.fileClosed(file); } } diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index c3a53857fae..7d27e926424 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -465,6 +465,18 @@ define(function (require, exports, module) { var document = EditorManager.getActiveEditor().document, fullPath = document.file.fullPath; + var fileType = (document.file instanceof InMemoryFile) ? "newFile" : "existingFile", + filelanguageName = lang ? lang._name : ""; + + HealthLogger.sendAnalyticsData( + HealthLogger.commonStrings.USAGE + HealthLogger.commonStrings.LANGUAGE_CHANGE + + filelanguageName + fileType, + HealthLogger.commonStrings.USAGE, + HealthLogger.commonStrings.LANGUAGE_CHANGE, + filelanguageName.toLowerCase(), + fileType + ); + if (lang === LANGUAGE_SET_AS_DEFAULT) { // Set file's current language in preferences as a file extension override (only enabled if not default already) var fileExtensionMap = PreferencesManager.get("language.fileExtensions"); diff --git a/src/languageTools/LanguageClientWrapper.js b/src/languageTools/LanguageClientWrapper.js index abd0c9b41f5..9cd7a5a42b1 100644 --- a/src/languageTools/LanguageClientWrapper.js +++ b/src/languageTools/LanguageClientWrapper.js @@ -358,7 +358,15 @@ define(function (require, exports, module) { */ //completion LanguageClientWrapper.prototype.requestHints = function (params) { - return this._request(ToolingInfo.FEATURES.CODE_HINTS, params); + return this._request(ToolingInfo.FEATURES.CODE_HINTS, params) + .then(function(response) { + if(response && response.items && response.items.length) { + logAnalyticsData("CODE_HINTS"); + } + return $.Deferred().resolve(response); + }, function(err) { + return $.Deferred().reject(err); + }); }; //completionItemResolve @@ -368,12 +376,28 @@ define(function (require, exports, module) { //signatureHelp LanguageClientWrapper.prototype.requestParameterHints = function (params) { - return this._request(ToolingInfo.FEATURES.PARAMETER_HINTS, params); + return this._request(ToolingInfo.FEATURES.PARAMETER_HINTS, params) + .then(function(response) { + if (response && response.signatures && response.signatures.length) { + logAnalyticsData("PARAM_HINTS"); + } + return $.Deferred().resolve(response); + }, function(err) { + return $.Deferred().reject(err); + }); }; //gotoDefinition LanguageClientWrapper.prototype.gotoDefinition = function (params) { - return this._request(ToolingInfo.FEATURES.JUMP_TO_DEFINITION, params); + return this._request(ToolingInfo.FEATURES.JUMP_TO_DEFINITION, params) + .then(function(response) { + if(response && response.range) { + logAnalyticsData("JUMP_TO_DEF"); + } + return $.Deferred().resolve(response); + }, function(err) { + return $.Deferred().reject(err); + }); }; //gotoDeclaration @@ -621,6 +645,23 @@ define(function (require, exports, module) { exports.LanguageClientWrapper = LanguageClientWrapper; + function logAnalyticsData(typeStrKey) { + var editor = require("editor/EditorManager").getActiveEditor(), + document = editor ? editor.document : null, + language = document ? document.language : null, + languageName = language ? language._name : "", + HealthLogger = require("utils/HealthLogger"), + typeStr = HealthLogger.commonStrings[typeStrKey] || ""; + + HealthLogger.sendAnalyticsData( + HealthLogger.commonStrings.USAGE + HealthLogger.commonStrings.LANGUAGE_SERVER_PROTOCOL + typeStr + languageName, + HealthLogger.commonStrings.USAGE, + HealthLogger.commonStrings.LANGUAGE_SERVER_PROTOCOL, + typeStr, + languageName.toLowerCase() + ); + } + //For unit testting exports.validateRequestParams = validateRequestParams; exports.validateNotificationParams = validateNotificationParams; diff --git a/src/utils/HealthLogger.js b/src/utils/HealthLogger.js index 6125314d616..3fd5e3d7e74 100644 --- a/src/utils/HealthLogger.js +++ b/src/utils/HealthLogger.js @@ -38,6 +38,17 @@ define(function (require, exports, module) { HEALTH_DATA_STATE_KEY = "HealthData.Logs", logHealthData = true; + var commonStrings = { USAGE: "usage", + FILE_OPEN: "fileOpen", + FILE_SAVE: "fileSave", + FILE_CLOSE: "fileClose", + LANGUAGE_CHANGE: "languageChange", + LANGUAGE_SERVER_PROTOCOL: "languageServerProtocol", + CODE_HINTS: "codeHints", + PARAM_HINTS: "parameterHints", + JUMP_TO_DEF: "jumpToDefinition" + }; + EventDispatcher.makeEventDispatcher(exports); /** @@ -165,6 +176,92 @@ define(function (require, exports, module) { fileEncCountMap[encoding]++; setHealthData(healthData); } + + + sendAnalyticsData(commonStrings.USAGE + commonStrings.FILE_OPEN + language._name, + commonStrings.USAGE, + commonStrings.FILE_OPEN, + language._name.toLowerCase() + ); + + } + + /** + * Whenever a file is saved call this function. + * The function will send the analytics Data + * We only log the standard filetypes and fileSize + * @param {String} filePath The path of the file to be registered + */ + function fileSaved(docToSave) { + if (!docToSave) { + return; + } + var fileType = docToSave.language ? docToSave.language._name : ""; + sendAnalyticsData(commonStrings.USAGE + commonStrings.FILE_SAVE + fileType, + commonStrings.USAGE, + commonStrings.FILE_SAVE, + fileType.toLowerCase() + ); + } + + /** + * Whenever a file is closed call this function. + * The function will send the analytics Data. + * We only log the standard filetypes and fileSize + * @param {String} filePath The path of the file to be registered + */ + function fileClosed(file) { + if (!file) { + return; + } + var language = LanguageManager.getLanguageForPath(file._path), + size = -1; + + function _sendData(fileSize) { + var subType = ""; + + if(fileSize/1024 <= 1) { + + if(fileSize < 0) { + subType = ""; + } + if(fileSize <= 10) { + subType = "Size_0_10KB"; + } else if (fileSize <= 50) { + subType = "Size_10_50KB"; + } else if (fileSize <= 100) { + subType = "Size_50_100KB"; + } else if (fileSize <= 500) { + subType = "Size_100_500KB"; + } else { + subType = "Size_500KB_1MB"; + } + + } else { + fileSize = fileSize/1024; + if(fileSize <= 2) { + subType = "Size_1_2MB"; + } else if(fileSize <= 5) { + subType = "Size_2_5MB"; + } else { + subType = "Size_Above_5MB"; + } + } + + sendAnalyticsData(commonStrings.USAGE + commonStrings.FILE_CLOSE + language._name + subType, + commonStrings.USAGE, + commonStrings.FILE_CLOSE, + language._name.toLowerCase(), + subType + ); + } + + file.stat(function(err, fileStat) { + if(!err) { + size = fileStat.size.valueOf()/1024; + } + _sendData(size); + }); } /** @@ -243,11 +340,14 @@ define(function (require, exports, module) { exports.getAggregatedHealthData = getAggregatedHealthData; exports.clearHealthData = clearHealthData; exports.fileOpened = fileOpened; + exports.fileSaved = fileSaved; + exports.fileClosed = fileClosed; exports.setProjectDetail = setProjectDetail; exports.searchDone = searchDone; exports.setHealthLogsEnabled = setHealthLogsEnabled; exports.shouldLogHealthData = shouldLogHealthData; exports.init = init; + exports.sendAnalyticsData = sendAnalyticsData; // constants // searchType for searchDone() @@ -262,5 +362,5 @@ define(function (require, exports, module) { exports.SEARCH_CASE_SENSITIVE = "searchCaseSensitive"; // A new search context on search bar up-Gives an idea of number of times user did a discrete search exports.SEARCH_NEW = "searchNew"; - exports.sendAnalyticsData = sendAnalyticsData; + exports.commonStrings = commonStrings; }); From b9c89f21cb0ea8d9e40dc981236eb8b1d3fdb063 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Wed, 10 Apr 2019 20:58:55 +0530 Subject: [PATCH 063/149] CodeHint Related Bug Fixes (#14692) --- .../default/PhpTooling/CodeHintsProvider.js | 15 ++++++++++++--- src/languageTools/DefaultProviders.js | 6 ++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/extensions/default/PhpTooling/CodeHintsProvider.js b/src/extensions/default/PhpTooling/CodeHintsProvider.js index 937f26476eb..784c4e21797 100644 --- a/src/extensions/default/PhpTooling/CodeHintsProvider.js +++ b/src/extensions/default/PhpTooling/CodeHintsProvider.js @@ -101,11 +101,15 @@ define(function (require, exports, module) { self.query = context.token.string.slice(0, context.pos.ch - context.token.start); if (msgObj) { - var res = msgObj.items || []; + var res = msgObj.items || [], + trimmedQuery = self.query.trim(), + hasIgnoreCharacters = self.ignoreQuery.includes(implicitChar) || self.ignoreQuery.includes(trimmedQuery), + isExplicitInvokation = implicitChar === null; + // There is a bug in Php Language Server, Php Language Server does not provide superGlobals // Variables as completion. so these variables are being explicity put in response objects // below code should be removed if php server fix this bug. - if(self.query) { + if((isExplicitInvokation || trimmedQuery) && !hasIgnoreCharacters) { for(var key in phpSuperGlobalVariables) { res.push({ label: key, @@ -115,7 +119,12 @@ define(function (require, exports, module) { } } - var filteredHints = filterWithQueryAndMatcher(res, self.query); + var filteredHints = []; + if (hasIgnoreCharacters || (isExplicitInvokation && !trimmedQuery)) { + filteredHints = filterWithQueryAndMatcher(res, ""); + } else { + filteredHints = filterWithQueryAndMatcher(res, self.query); + } StringMatch.basicMatchSort(filteredHints); filteredHints.forEach(function (element) { diff --git a/src/languageTools/DefaultProviders.js b/src/languageTools/DefaultProviders.js index 6339ace3277..fbeb67e36ec 100644 --- a/src/languageTools/DefaultProviders.js +++ b/src/languageTools/DefaultProviders.js @@ -46,6 +46,7 @@ define(function (require, exports, module) { function CodeHintsProvider(client) { this.client = client; this.query = ""; + this.ignoreQuery = ["-", "->", ">", ":", "::", "(", "()", ")", "[", "[]", "]", "{", "{}", "}"]; } function formatTypeDataForToken($hintObj, token) { @@ -160,11 +161,12 @@ define(function (require, exports, module) { token = $hint.data("token"), txt = null, query = this.query, + shouldIgnoreQuery = this.ignoreQuery.includes(query), + inclusion = shouldIgnoreQuery ? "" : query, start = { line: cursor.line, - ch: cursor.ch - query.length + ch: cursor.ch - inclusion.length }, - end = { line: cursor.line, ch: cursor.ch From 6b84fa4889f80565438dd92c2235aab68c57561b Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Fri, 12 Apr 2019 05:59:48 +0000 Subject: [PATCH 064/149] Updated by ALF automation. --- src/nls/fr/strings.js | 25 +++++++++++++++++++------ src/nls/ja/strings.js | 23 ++++++++++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index bb199373bcc..ceaf40070d1 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -650,10 +650,10 @@ define({ // extensions/default/HealthData "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "Rapport d’intégrité de {APP_NAME}", - "HEALTH_DATA_DO_TRACK": "Partager des informations anonymes sur la façon dont j’utilise {APP_NAME}", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "

Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et anonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", + "HEALTH_DATA_DO_TRACK": "Partager des informations pseudonymes sur la façon dont j’utilise {APP_NAME}", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", "HEALTH_DATA_PREVIEW": "Rapport d’intégrité de {APP_NAME}", - "HEALTH_DATA_PREVIEW_INTRO": "

Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et anonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

", + "HEALTH_DATA_PREVIEW_INTRO": "

Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "Temps", @@ -872,8 +872,21 @@ define({ "DESCRIPTION_AUTO_UPDATE": "Activer/désactiver la mise à jour automatique de Brackets", "AUTOUPDATE_ERROR": "Erreur !", "AUTOUPDATE_IN_PROGRESS": "Une mise à jour est déjà en cours.", - - "NUMBER_WITH_PERCENTAGE": "{0}%", + + "NUMBER_WITH_PERCENTAGE": "{0} %", // Strings for Related Files - "CMD_FIND_RELATED_FILES": "Trouver les fichiers associés" + "CMD_FIND_RELATED_FILES": "Trouver les fichiers associés", + + ///String for Php Tooling Extensions + "PHP_VERSION_INVALID": "Erreur lors de l’analyse de la version de PHP. Veuillez vérifier la sortie de la commande « php –version ».", + "PHP_UNSUPPORTED_VERSION": "Installez le moteur d’exécution de PHP 7 pour activer les outils correspondants comme Conseils de code, Conseils de paramètres, Accéder à la définition, etc. Version trouvée : {0}", + "PHP_EXECUTABLE_NOT_FOUND": "Moteur d’exécution PHP introuvable. Installez le moteur d’exécution de PHP 7 et mettez à jour « executablePath » dans les préférences PHP. Cela permettra l’activation des outils liés à PHP comme Conseils de code, Conseils de paramètres ou encore Accéder à la définition.", + "PHP_PROCESS_SPAWN_ERROR": "Code d’erreur {0} rencontré lors du démarrage du processus PHP.", + "PHP_SERVER_ERROR_TITLE": "Erreur", + "PHP_SERVER_MEMORY_LIMIT_INVALID": "La limite de mémoire que vous avez fournie n’est pas valide. Veuillez corriger la valeur indiquée dans les préférences PHP.", + "DESCRIPTION_PHP_TOOLING_CONFIGURATION": "Paramètres de configuration par défaut des outils PHP", + "OPEN_PREFERENNCES": "Ouvrir les préférences" + + //Strings for LanguageTools Preferences + LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 85428d85fcb..2876692d821 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -650,10 +650,10 @@ define({ // extensions/default/HealthData "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する情報を匿名で共有します", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた匿名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

お客様のデータを確認するには、または、データを共有しないように選択するには、ヘルプ/正常性レポートを選択してください。

{APP_NAME} の正常性レポートに関する詳細情報", + "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する偽名情報を共有します", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

お客様のデータを確認するには、または、データを共有しないように選択するには、ヘルプ/正常性レポートを選択してください。

{APP_NAME} の正常性レポートに関する詳細情報", "HEALTH_DATA_PREVIEW": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_PREVIEW_INTRO": "

{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた匿名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについて、またこれが {APP_NAME} コミュニティにどのように役立ち、プライバシーを保護するかついて詳細をご確認ください。

有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

", + "HEALTH_DATA_PREVIEW_INTRO": "

{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについて、またこれが {APP_NAME} コミュニティにどのように役立ち、プライバシーを保護するかついて詳細をご確認ください。

有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "時間", @@ -872,8 +872,21 @@ define({ "DESCRIPTION_AUTO_UPDATE": "Brackets の自動更新を有効化/無効化", "AUTOUPDATE_ERROR": "エラー!", "AUTOUPDATE_IN_PROGRESS": "更新は既に進行中です。", - + "NUMBER_WITH_PERCENTAGE": "{0}%", // Strings for Related Files - "CMD_FIND_RELATED_FILES": "関連するファイルを検索" + "CMD_FIND_RELATED_FILES": "関連するファイルを検索", + + ///String for Php Tooling Extensions + "PHP_VERSION_INVALID": "PHP バージョンを解析する際のエラーです。“php –version” コマンドの出力を確認してください。", + "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールします。検出されたバージョン: {0}", + "PHP_EXECUTABLE_NOT_FOUND": "PHP ランタイムが見つかりません。PHP7 ランタイムをインストールして、PHP の環境設定で適切に “executablePath” を更新してください。これにより、コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールが有効になります。", + "PHP_PROCESS_SPAWN_ERROR": "PHP プロセスを起動中に、エラーコード {0} が発生しました。", + "PHP_SERVER_ERROR_TITLE": "エラー", + "PHP_SERVER_MEMORY_LIMIT_INVALID": "指定したメモリ制限は無効です。正しい値を設定するために、PHP の環境設定を確認してください。", + "DESCRIPTION_PHP_TOOLING_CONFIGURATION": "PHP ツールのデフォルト設定", + "OPEN_PREFERENNCES": "環境設定を開く" + + //Strings for LanguageTools Preferences + LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" }); From 55de6c62e3ee2c79cef012619e392000666c6396 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Fri, 12 Apr 2019 17:16:57 +0530 Subject: [PATCH 065/149] Fixing the LANGUAGE_TOOLS_PREFERENCES key in strings.js --- src/nls/fr/strings.js | 4 ++-- src/nls/ja/strings.js | 4 ++-- src/nls/root/strings.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index ceaf40070d1..714f9ebb792 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -885,8 +885,8 @@ define({ "PHP_SERVER_ERROR_TITLE": "Erreur", "PHP_SERVER_MEMORY_LIMIT_INVALID": "La limite de mémoire que vous avez fournie n’est pas valide. Veuillez corriger la valeur indiquée dans les préférences PHP.", "DESCRIPTION_PHP_TOOLING_CONFIGURATION": "Paramètres de configuration par défaut des outils PHP", - "OPEN_PREFERENNCES": "Ouvrir les préférences" + "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 2876692d821..0597c57ae84 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -885,8 +885,8 @@ define({ "PHP_SERVER_ERROR_TITLE": "エラー", "PHP_SERVER_MEMORY_LIMIT_INVALID": "指定したメモリ制限は無効です。正しい値を設定するために、PHP の環境設定を確認してください。", "DESCRIPTION_PHP_TOOLING_CONFIGURATION": "PHP ツールのデフォルト設定", - "OPEN_PREFERENNCES": "環境設定を開く" + "OPEN_PREFERENNCES": "環境設定を開く", //Strings for LanguageTools Preferences - LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" }); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index c66291168a0..a3afc09921b 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -888,5 +888,5 @@ define({ "OPEN_PREFERENNCES" : "Open Preferences", //Strings for LanguageTools Preferences - LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" }); From 69a162747c03167c7861b85cd9c8d43f827af11d Mon Sep 17 00:00:00 2001 From: walfgithub Date: Fri, 12 Apr 2019 05:23:33 -0700 Subject: [PATCH 066/149] ALF Automation (#14699) * Updated by ALF automation. * Fixing the LANGUAGE_TOOLS_PREFERENCES key in strings.js --- src/nls/fr/strings.js | 25 +++++++++++++++++++------ src/nls/ja/strings.js | 23 ++++++++++++++++++----- src/nls/root/strings.js | 2 +- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index bb199373bcc..714f9ebb792 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -650,10 +650,10 @@ define({ // extensions/default/HealthData "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "Rapport d’intégrité de {APP_NAME}", - "HEALTH_DATA_DO_TRACK": "Partager des informations anonymes sur la façon dont j’utilise {APP_NAME}", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "

Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et anonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", + "HEALTH_DATA_DO_TRACK": "Partager des informations pseudonymes sur la façon dont j’utilise {APP_NAME}", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", "HEALTH_DATA_PREVIEW": "Rapport d’intégrité de {APP_NAME}", - "HEALTH_DATA_PREVIEW_INTRO": "

Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et anonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

", + "HEALTH_DATA_PREVIEW_INTRO": "

Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "Temps", @@ -872,8 +872,21 @@ define({ "DESCRIPTION_AUTO_UPDATE": "Activer/désactiver la mise à jour automatique de Brackets", "AUTOUPDATE_ERROR": "Erreur !", "AUTOUPDATE_IN_PROGRESS": "Une mise à jour est déjà en cours.", - - "NUMBER_WITH_PERCENTAGE": "{0}%", + + "NUMBER_WITH_PERCENTAGE": "{0} %", // Strings for Related Files - "CMD_FIND_RELATED_FILES": "Trouver les fichiers associés" + "CMD_FIND_RELATED_FILES": "Trouver les fichiers associés", + + ///String for Php Tooling Extensions + "PHP_VERSION_INVALID": "Erreur lors de l’analyse de la version de PHP. Veuillez vérifier la sortie de la commande « php –version ».", + "PHP_UNSUPPORTED_VERSION": "Installez le moteur d’exécution de PHP 7 pour activer les outils correspondants comme Conseils de code, Conseils de paramètres, Accéder à la définition, etc. Version trouvée : {0}", + "PHP_EXECUTABLE_NOT_FOUND": "Moteur d’exécution PHP introuvable. Installez le moteur d’exécution de PHP 7 et mettez à jour « executablePath » dans les préférences PHP. Cela permettra l’activation des outils liés à PHP comme Conseils de code, Conseils de paramètres ou encore Accéder à la définition.", + "PHP_PROCESS_SPAWN_ERROR": "Code d’erreur {0} rencontré lors du démarrage du processus PHP.", + "PHP_SERVER_ERROR_TITLE": "Erreur", + "PHP_SERVER_MEMORY_LIMIT_INVALID": "La limite de mémoire que vous avez fournie n’est pas valide. Veuillez corriger la valeur indiquée dans les préférences PHP.", + "DESCRIPTION_PHP_TOOLING_CONFIGURATION": "Paramètres de configuration par défaut des outils PHP", + "OPEN_PREFERENNCES": "Ouvrir les préférences", + + //Strings for LanguageTools Preferences + "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 85428d85fcb..0597c57ae84 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -650,10 +650,10 @@ define({ // extensions/default/HealthData "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する情報を匿名で共有します", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた匿名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

お客様のデータを確認するには、または、データを共有しないように選択するには、ヘルプ/正常性レポートを選択してください。

{APP_NAME} の正常性レポートに関する詳細情報", + "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する偽名情報を共有します", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

お客様のデータを確認するには、または、データを共有しないように選択するには、ヘルプ/正常性レポートを選択してください。

{APP_NAME} の正常性レポートに関する詳細情報", "HEALTH_DATA_PREVIEW": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_PREVIEW_INTRO": "

{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた匿名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについて、またこれが {APP_NAME} コミュニティにどのように役立ち、プライバシーを保護するかついて詳細をご確認ください。

有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

", + "HEALTH_DATA_PREVIEW_INTRO": "

{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについて、またこれが {APP_NAME} コミュニティにどのように役立ち、プライバシーを保護するかついて詳細をご確認ください。

有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "時間", @@ -872,8 +872,21 @@ define({ "DESCRIPTION_AUTO_UPDATE": "Brackets の自動更新を有効化/無効化", "AUTOUPDATE_ERROR": "エラー!", "AUTOUPDATE_IN_PROGRESS": "更新は既に進行中です。", - + "NUMBER_WITH_PERCENTAGE": "{0}%", // Strings for Related Files - "CMD_FIND_RELATED_FILES": "関連するファイルを検索" + "CMD_FIND_RELATED_FILES": "関連するファイルを検索", + + ///String for Php Tooling Extensions + "PHP_VERSION_INVALID": "PHP バージョンを解析する際のエラーです。“php –version” コマンドの出力を確認してください。", + "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールします。検出されたバージョン: {0}", + "PHP_EXECUTABLE_NOT_FOUND": "PHP ランタイムが見つかりません。PHP7 ランタイムをインストールして、PHP の環境設定で適切に “executablePath” を更新してください。これにより、コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールが有効になります。", + "PHP_PROCESS_SPAWN_ERROR": "PHP プロセスを起動中に、エラーコード {0} が発生しました。", + "PHP_SERVER_ERROR_TITLE": "エラー", + "PHP_SERVER_MEMORY_LIMIT_INVALID": "指定したメモリ制限は無効です。正しい値を設定するために、PHP の環境設定を確認してください。", + "DESCRIPTION_PHP_TOOLING_CONFIGURATION": "PHP ツールのデフォルト設定", + "OPEN_PREFERENNCES": "環境設定を開く", + + //Strings for LanguageTools Preferences + "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" }); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index c66291168a0..a3afc09921b 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -888,5 +888,5 @@ define({ "OPEN_PREFERENNCES" : "Open Preferences", //Strings for LanguageTools Preferences - LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" }); From 228ac2806746eeb1ca11eb9d348d71f906d7e110 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Fri, 12 Apr 2019 20:00:10 +0530 Subject: [PATCH 067/149] PHPCodeHints : Select initial element in the hint list (#14694) * Select initial hint while showing hints * Only selecting initial hint if token is not blank --- src/extensions/default/PhpTooling/CodeHintsProvider.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/PhpTooling/CodeHintsProvider.js b/src/extensions/default/PhpTooling/CodeHintsProvider.js index 784c4e21797..395c9c599f2 100644 --- a/src/extensions/default/PhpTooling/CodeHintsProvider.js +++ b/src/extensions/default/PhpTooling/CodeHintsProvider.js @@ -151,8 +151,10 @@ define(function (require, exports, module) { }); } + var token = self.query; $deferredHints.resolve({ - "hints": hints + "hints": hints, + "selectInitial": token && /\S/.test(token) && isNaN(parseInt(token, 10)) // If the active token is blank then don't put default selection }); }).fail(function () { $deferredHints.reject(); From 379a26b64973592fe9811a2fad194d1584d7c4b7 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Fri, 12 Apr 2019 21:38:36 +0530 Subject: [PATCH 068/149] Fix QuickOpen Bug where an Error occur on dismissing the Modal when no Document is opene (#14698) --- src/search/QuickOpen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search/QuickOpen.js b/src/search/QuickOpen.js index 21255cc5f41..5c9f105292f 100644 --- a/src/search/QuickOpen.js +++ b/src/search/QuickOpen.js @@ -367,10 +367,10 @@ define(function (require, exports, module) { // completes) since ModalBar has already resized the editor and done its own scroll adjustment before // this event fired - so anything we set here will override the pos that was (re)set by ModalBar. var editor = EditorManager.getCurrentFullEditor(); - if (this._origSelections) { + if (editor && this._origSelections) { editor.setSelections(this._origSelections); } - if (this._origScrollPos) { + if (editor && this._origScrollPos) { editor.setScrollPos(this._origScrollPos.x, this._origScrollPos.y); } } From 629d8dc02d9055104208b6d0a94bc1167c30f591 Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Fri, 12 Apr 2019 16:56:00 +0000 Subject: [PATCH 069/149] Updated by ALF automation. --- src/nls/fr/strings.js | 2 +- src/nls/ja/strings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 714f9ebb792..f69da6a973b 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -888,5 +888,5 @@ define({ "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 0597c57ae84..4e50b038526 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -888,5 +888,5 @@ define({ "OPEN_PREFERENNCES": "環境設定を開く", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" }); From 6f80d3c8c26f5f85c145d6f3c6bfd02c6510e4f6 Mon Sep 17 00:00:00 2001 From: Snchit Grover Date: Fri, 12 Apr 2019 23:48:12 +0530 Subject: [PATCH 070/149] Changes to send data only once per 24 hours (#14695) * Changes to send data only once per 24 hours * Update HealthLogger.js --- .../default/HealthData/HealthDataManager.js | 25 +++++++++++-------- src/utils/HealthLogger.js | 8 ++++-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/extensions/default/HealthData/HealthDataManager.js b/src/extensions/default/HealthData/HealthDataManager.js index a452a4e5471..e6a72b5a181 100644 --- a/src/extensions/default/HealthData/HealthDataManager.js +++ b/src/extensions/default/HealthData/HealthDataManager.js @@ -308,22 +308,16 @@ define(function (require, exports, module) { isHDTracking = prefs.get("healthDataTracking"), isEventDataAlreadySent; - var options = { - location: { - scope: "default" - } - }; - if (isHDTracking) { - isEventDataAlreadySent = PreferencesManager.getViewState(Eventparams.eventName); - PreferencesManager.setViewState(Eventparams.eventName, 1, options); + isEventDataAlreadySent = HealthLogger.analyticsEventMap.get(Eventparams.eventName); + HealthLogger.analyticsEventMap.set(Eventparams.eventName, true); if (!isEventDataAlreadySent || forceSend) { sendAnalyticsDataToServer(Eventparams) .done(function () { - PreferencesManager.setViewState(Eventparams.eventName, 1, options); + HealthLogger.analyticsEventMap.set(Eventparams.eventName, true); result.resolve(); }).fail(function () { - PreferencesManager.setViewState(Eventparams.eventName, 0, options); + HealthLogger.analyticsEventMap.set(Eventparams.eventName, false); result.reject(); }); } else { @@ -336,6 +330,17 @@ define(function (require, exports, module) { return result.promise(); } + /** + * This function is auto called after 24 hours to empty the map + * Map is used to make sure that we send an event only once per 24 hours + **/ + + function emptyAnalyticsMap() { + HealthLogger.analyticsEventMap.clear(); + setTimeout(emptyAnalyticsMap, ONE_DAY); + } + setTimeout(emptyAnalyticsMap, ONE_DAY); + // Expose a command to test data sending capability, but limit it to dev environment only CommandManager.register("Sends health data and Analytics data for testing purpose", "sendHealthData", function() { if (brackets.config.environment === "stage") { diff --git a/src/utils/HealthLogger.js b/src/utils/HealthLogger.js index 3fd5e3d7e74..1ac31aa182a 100644 --- a/src/utils/HealthLogger.js +++ b/src/utils/HealthLogger.js @@ -24,6 +24,7 @@ /** * Utilities functions related to Health Data logging */ +/*global Map*/ define(function (require, exports, module) { "use strict"; @@ -36,7 +37,8 @@ define(function (require, exports, module) { EventDispatcher = require("utils/EventDispatcher"), HEALTH_DATA_STATE_KEY = "HealthData.Logs", - logHealthData = true; + logHealthData = true, + analyticsEventMap = new Map(); var commonStrings = { USAGE: "usage", FILE_OPEN: "fileOpen", @@ -319,9 +321,10 @@ define(function (require, exports, module) { * needs to be logged- should be a js var compatible string */ function sendAnalyticsData(eventName, eventCategory, eventSubCategory, eventType, eventSubType) { - var isEventDataAlreadySent = PreferencesManager.getViewState(eventName), + var isEventDataAlreadySent = analyticsEventMap.get(eventName), isHDTracking = PreferencesManager.getExtensionPrefs("healthData").get("healthDataTracking"), eventParams = {}; + if (isHDTracking && !isEventDataAlreadySent && eventName && eventCategory) { eventParams = { eventName: eventName, @@ -363,4 +366,5 @@ define(function (require, exports, module) { // A new search context on search bar up-Gives an idea of number of times user did a discrete search exports.SEARCH_NEW = "searchNew"; exports.commonStrings = commonStrings; + exports.analyticsEventMap = analyticsEventMap; }); From aa085a16e0f09eaa8929011285c5a88e5f06c871 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Fri, 12 Apr 2019 23:49:00 +0530 Subject: [PATCH 071/149] Sending New File Save/Open Analytics Data (#14700) --- src/document/DocumentCommandHandlers.js | 10 ++++++++++ src/utils/HealthLogger.js | 1 + 2 files changed, 11 insertions(+) diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index 832148befb4..ed875376d9f 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -689,6 +689,15 @@ define(function (require, exports, module) { var doc = DocumentManager.createUntitledDocument(_nextUntitledIndexToUse++, defaultExtension); MainViewManager._edit(MainViewManager.ACTIVE_PANE, doc); + HealthLogger.sendAnalyticsData( + HealthLogger.commonStrings.USAGE + + HealthLogger.commonStrings.FILE_OPEN + + HealthLogger.commonStrings.FILE_NEW, + HealthLogger.commonStrings.USAGE, + HealthLogger.commonStrings.FILE_OPEN, + HealthLogger.commonStrings.FILE_NEW + ); + return new $.Deferred().resolve(doc).promise(); } @@ -968,6 +977,7 @@ define(function (require, exports, module) { } else { openNewFile(); } + HealthLogger.fileSaved(doc); }) .fail(function (error) { _showSaveFileError(error, path) diff --git a/src/utils/HealthLogger.js b/src/utils/HealthLogger.js index 1ac31aa182a..48d07ef0bce 100644 --- a/src/utils/HealthLogger.js +++ b/src/utils/HealthLogger.js @@ -42,6 +42,7 @@ define(function (require, exports, module) { var commonStrings = { USAGE: "usage", FILE_OPEN: "fileOpen", + FILE_NEW: "newfile", FILE_SAVE: "fileSave", FILE_CLOSE: "fileClose", LANGUAGE_CHANGE: "languageChange", From f1a5f23967e116363fc037e3bb4f7be507aa5512 Mon Sep 17 00:00:00 2001 From: walfgithub Date: Fri, 12 Apr 2019 11:19:56 -0700 Subject: [PATCH 072/149] ALF Automation (#14701) * Updated by ALF automation. * Fixing the LANGUAGE_TOOLS_PREFERENCES key in strings.js * Updated by ALF automation. --- src/nls/fr/strings.js | 2 +- src/nls/ja/strings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 714f9ebb792..f69da6a973b 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -888,5 +888,5 @@ define({ "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 0597c57ae84..4e50b038526 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -888,5 +888,5 @@ define({ "OPEN_PREFERENNCES": "環境設定を開く", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" }); From 4e55d2a150cde4c664678b11006a045d0e16ec72 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Sun, 14 Apr 2019 19:56:21 +0530 Subject: [PATCH 073/149] Adding Code Hints Description Box for handling additional hint information in LSP (#14696) * Code Hint Desc first cut * Remove extra code * Fixing Lint Issues * Undo ESLINT Fixes * Fix CSS Reflow issue * Moving styles to brackets core --- src/editor/CodeHintList.js | 25 +++++++- src/editor/CodeHintManager.js | 19 ++++-- .../default/PhpTooling/CodeHintsProvider.js | 64 +++++++++++++------ src/styles/brackets_core_ui_variables.less | 10 ++- src/styles/brackets_patterns_override.less | 42 ++++++++++++ 5 files changed, 134 insertions(+), 26 deletions(-) diff --git a/src/editor/CodeHintList.js b/src/editor/CodeHintList.js index 964e1ed447c..5a132281d75 100644 --- a/src/editor/CodeHintList.js +++ b/src/editor/CodeHintList.js @@ -152,7 +152,7 @@ define(function (require, exports, module) { ViewUtils.scrollElementIntoView($view, $item, false); if (this.handleHighlight) { - this.handleHighlight($item.find("a")); + this.handleHighlight($item.find("a"), this.$hintMenu.find("#codehint-desc")); } } }; @@ -191,6 +191,7 @@ define(function (require, exports, module) { this.hints = hintObj.hints; this.hints.handleWideResults = hintObj.handleWideResults; + this.enableDescription = hintObj.enableDescription; // if there is no match, assume name is already a formatted jQuery // object; otherwise, use match to format name for display. @@ -265,6 +266,17 @@ define(function (require, exports, module) { // attach to DOM $parent.append($ul); + $parent.find(".hint-list-offset").remove(); + $("
").appendTo($parent); + + // If a a description field requested attach one + if (this.enableDescription) { + // Remove the desc element first to ensure DOM order + $parent.find("#codehint-desc").remove(); + $parent.append(""); + $ul.addClass("withDesc"); + $parent.find(".hint-list-offset").addClass("withDesc"); + } this._setSelectedIndex(selectInitial ? 0 : -1); } }; @@ -283,7 +295,9 @@ define(function (require, exports, module) { textHeight = this.editor.getTextHeight(), $window = $(window), $menuWindow = this.$hintMenu.children("ul"), - menuHeight = $menuWindow.outerHeight(); + $descElement = this.$hintMenu.find("#codehint-desc"), + descOverhang = $descElement.length === 1 ? $descElement.height() : 0, + menuHeight = $menuWindow.outerHeight() + descOverhang; // TODO Ty: factor out menu repositioning logic so code hints and Context menus share code // adjust positioning so menu is not clipped off bottom or right @@ -304,6 +318,13 @@ define(function (require, exports, module) { availableWidth = menuWidth + Math.abs(rightOverhang); } + //Creating the offset element for hint description element + var descOffset = this.$hintMenu.find("ul.dropdown-menu")[0].getBoundingClientRect().height; + if (descOffset === 0) { + descOffset = menuHeight - descOverhang; + } + this.$hintMenu.find(".hint-list-offset").css("height", descOffset - 1); + return {left: posLeft, top: posTop, width: availableWidth}; }; diff --git a/src/editor/CodeHintManager.js b/src/editor/CodeHintManager.js index 8a2cdf28e69..925ffcb61ec 100644 --- a/src/editor/CodeHintManager.js +++ b/src/editor/CodeHintManager.js @@ -507,10 +507,21 @@ define(function (require, exports, module) { sessionEditor = editor; hintList = new CodeHintList(sessionEditor, insertHintOnTab, maxCodeHints); - hintList.onHighlight(function ($hint) { - // If the current hint provider listening for hint item highlight change - if (sessionProvider.onHighlight) { - sessionProvider.onHighlight($hint); + hintList.onHighlight(function ($hint, $hintDescContainer) { + if (hintList.enableDescription && $hintDescContainer && $hintDescContainer.length) { + // If the current hint provider listening for hint item highlight change + if (sessionProvider.onHighlight) { + sessionProvider.onHighlight($hint, $hintDescContainer); + } + + // Update the hint description + if (sessionProvider.updateHintDescription) { + sessionProvider.updateHintDescription($hint, $hintDescContainer); + } + } else { + if (sessionProvider.onHighlight) { + sessionProvider.onHighlight($hint); + } } }); hintList.onSelect(function (hint) { diff --git a/src/extensions/default/PhpTooling/CodeHintsProvider.js b/src/extensions/default/PhpTooling/CodeHintsProvider.js index 395c9c599f2..3f54d82f2e1 100644 --- a/src/extensions/default/PhpTooling/CodeHintsProvider.js +++ b/src/extensions/default/PhpTooling/CodeHintsProvider.js @@ -36,30 +36,27 @@ define(function (require, exports, module) { preferPrefixMatches: true }); - var phpSuperGlobalVariables = JSON.parse(require("text!phpGlobals.json")); + var phpSuperGlobalVariables = JSON.parse(require("text!phpGlobals.json")), + hintType = { + "2": "Method", + "3": "Function", + "4": "Constructor", + "6": "Variable", + "7": "Class", + "8": "Interface", + "9": "Module", + "10": "Property", + "14": "Keyword", + "21": "Constant" + }; function CodeHintsProvider(client) { this.defaultCodeHintProviders = new DefaultProviders.CodeHintsProvider(client); } - function formatTypeDataForToken($hintObj, token) { + function setStyleAndCacheToken($hintObj, token) { $hintObj.addClass('brackets-hints-with-type-details'); - if (token.detail) { - if (token.detail.trim() !== '?') { - if (token.detail.length < 30) { - $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("brackets-hints-type-details"); - } - $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("hint-description"); - } - } else { - if (token.keyword) { - $('keyword').appendTo($hintObj).addClass("brackets-hints-keyword"); - } - } - if (token.documentation) { - $hintObj.attr('title', token.documentation); - $('').text(token.documentation.trim()).appendTo($hintObj).addClass("hint-doc"); - } + $hintObj.data('completionItem', token); } function filterWithQueryAndMatcher(hints, query) { @@ -146,7 +143,7 @@ define(function (require, exports, module) { } $fHint.data("token", element); - formatTypeDataForToken($fHint, element); + setStyleAndCacheToken($fHint, element); hints.push($fHint); }); } @@ -154,6 +151,7 @@ define(function (require, exports, module) { var token = self.query; $deferredHints.resolve({ "hints": hints, + "enableDescription": true, "selectInitial": token && /\S/.test(token) && isNaN(parseInt(token, 10)) // If the active token is blank then don't put default selection }); }).fail(function () { @@ -167,5 +165,33 @@ define(function (require, exports, module) { return this.defaultCodeHintProviders.insertHint($hint); }; + CodeHintsProvider.prototype.updateHintDescription = function ($hint, $hintDescContainer) { + var $hintObj = $hint.find('.brackets-hints-with-type-details'), + token = $hintObj.data('completionItem'), + $desc = $('
'); + + if(!token) { + $hintDescContainer.empty(); + return; + } + + if (token.detail) { + if (token.detail.trim() !== '?') { + $('
' + token.detail.split('->').join(':').toString().trim() + '
').appendTo($desc).addClass("codehint-desc-type-details"); + } + } else { + if (hintType[token.kind]) { + $('
' + hintType[token.kind] + '
').appendTo($desc).addClass("codehint-desc-type-details"); + } + } + if (token.documentation) { + $('
').html(token.documentation.trim()).appendTo($desc).addClass("codehint-desc-documentation"); + } + + //To ensure CSS reflow doesn't cause a flicker. + $hintDescContainer.empty(); + $hintDescContainer.append($desc); + }; + exports.CodeHintsProvider = CodeHintsProvider; }); diff --git a/src/styles/brackets_core_ui_variables.less b/src/styles/brackets_core_ui_variables.less index 3824aa242b2..f6290ce0c0f 100644 --- a/src/styles/brackets_core_ui_variables.less +++ b/src/styles/brackets_core_ui_variables.less @@ -124,7 +124,10 @@ @button-icon: "images/find-replace-sprites.svg"; @jstree-sprite: url("images/jsTreeSprites.svg") !important; - +// Codehint description +@bc-codehint-desc: #e6e6e6; +@bc-codehint-desc-type-details: #1473e6; +@bc-codehint-desc-documentation:#424242; /* Dark Core UI variables -----------------------------------------------------------------------------*/ @@ -213,3 +216,8 @@ // images @dark-button-icon: "images/find-replace-sprites-dark.svg"; @dark-jstree-sprite: url("images/jsTreeSprites-dark.svg") !important; + +// Codehint description +@dark-bc-codehint-desc: #2c2c2c; +@dark-bc-codehint-desc-type-details: #46a0f5; +@dark-bc-codehint-desc-documentation:#b1b1b1; diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less index 44c2879a7a6..e1de2ba3902 100644 --- a/src/styles/brackets_patterns_override.less +++ b/src/styles/brackets_patterns_override.less @@ -584,6 +584,48 @@ a:focus { transition: right 167ms, left 167ms; } +#codehint-desc { + background: @bc-codehint-desc; + position: relative; + width: 100%; + box-sizing: border-box; + float: left; + top: 34px; + border-radius: 1px !important; + border-top-left-radius: 0px !important; + border-top-right-radius: 0px !important; + overflow-x: hidden; + min-height: 30px; + max-height: 120px !important; + + .dark & { + background: @dark-bc-codehint-desc; + } +} + +.codehint-desc-type-details { + padding: 5px 15px 5px 15px; + color: @bc-codehint-desc-type-details; + font-weight: bold; + font-size: 1.2em; + line-height: inherit; + + .dark & { + color: @dark-bc-codehint-desc-type-details; + } +} + +.codehint-desc-documentation { + padding: 5px 15px 5px 15px; + color: @bc-codehint-desc-documentation; + font-size: 1.1em; + white-space: pre-wrap; + + .dark & { + color: @dark-bc-codehint-desc-documentation; + } +} + #context-menu-bar { margin: 0; } From dc8d9307ba52e7dc645c759d5a5a60d2db02c915 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Sun, 14 Apr 2019 19:57:54 +0530 Subject: [PATCH 074/149] Capability to Support Document & Project Symbols provided by a Language Server (#14697) * Symbols Provider First Cut * Linting the Symbols Provider * Symbols Provider Second Cut * Document Symbol and Project Symbol support in Brackets * Minor Fixes * Removing Typo, and adding null checks * Addressing review comments * Handle menu toggling in case of language change * Remove active document check * Add unittests for QuickOpen Symbols Provider --- src/base-config/keyboard.json | 3 + src/command/Commands.js | 1 + src/command/DefaultMenus.js | 1 + .../default/PhpTooling/PHPSymbolProviders.js | 238 ++++++++++++++++++ src/extensions/default/PhpTooling/main.js | 32 ++- .../PhpTooling/unittest-files/test/test4.php | 13 + .../default/PhpTooling/unittests.js | 130 +++++++++- src/languageTools/LanguageClientWrapper.js | 2 +- src/nls/root/strings.js | 5 +- src/search/QuickOpen.js | 179 ++++++++++--- 10 files changed, 566 insertions(+), 38 deletions(-) create mode 100644 src/extensions/default/PhpTooling/PHPSymbolProviders.js create mode 100644 src/extensions/default/PhpTooling/unittest-files/test/test4.php diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json index aa9c67cd4d5..07611fea90c 100644 --- a/src/base-config/keyboard.json +++ b/src/base-config/keyboard.json @@ -281,6 +281,9 @@ "navigate.gotoDefinition": [ "Ctrl-T" ], + "navigate.gotoDefinitionInProject": [ + "Ctrl-Shift-T" + ], "navigate.jumptoDefinition": [ "Ctrl-J" ], diff --git a/src/command/Commands.js b/src/command/Commands.js index dc147eeac6f..e5811dd229c 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -125,6 +125,7 @@ define(function (require, exports, module) { exports.NAVIGATE_QUICK_OPEN = "navigate.quickOpen"; // QuickOpen.js doFileSearch() exports.NAVIGATE_JUMPTO_DEFINITION = "navigate.jumptoDefinition"; // JumpToDefManager.js _doJumpToDef() exports.NAVIGATE_GOTO_DEFINITION = "navigate.gotoDefinition"; // QuickOpen.js doDefinitionSearch() + exports.NAVIGATE_GOTO_DEFINITION_PROJECT = "navigate.gotoDefinitionInProject"; // QuickOpen.js doDefinitionSearchInProject() exports.NAVIGATE_GOTO_LINE = "navigate.gotoLine"; // QuickOpen.js doGotoLine() exports.NAVIGATE_GOTO_FIRST_PROBLEM = "navigate.gotoFirstProblem"; // CodeInspection.js handleGotoFirstProblem() exports.TOGGLE_QUICK_EDIT = "navigate.toggleQuickEdit"; // EditorManager.js _toggleInlineWidget() diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js index 2eb6f871925..912d9eaa306 100644 --- a/src/command/DefaultMenus.js +++ b/src/command/DefaultMenus.js @@ -172,6 +172,7 @@ define(function (require, exports, module) { menu.addMenuItem(Commands.NAVIGATE_QUICK_OPEN); menu.addMenuItem(Commands.NAVIGATE_GOTO_LINE); menu.addMenuItem(Commands.NAVIGATE_GOTO_DEFINITION); + menu.addMenuItem(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT); menu.addMenuItem(Commands.NAVIGATE_JUMPTO_DEFINITION); menu.addMenuItem(Commands.NAVIGATE_GOTO_FIRST_PROBLEM); menu.addMenuDivider(); diff --git a/src/extensions/default/PhpTooling/PHPSymbolProviders.js b/src/extensions/default/PhpTooling/PHPSymbolProviders.js new file mode 100644 index 00000000000..8517e1db574 --- /dev/null +++ b/src/extensions/default/PhpTooling/PHPSymbolProviders.js @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019 - present Adobe. 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 regexp: true */ + +define(function (require, exports, module) { + "use strict"; + + var EditorManager = brackets.getModule("editor/EditorManager"), + QuickOpen = brackets.getModule("search/QuickOpen"), + Commands = brackets.getModule("command/Commands"), + CommandManager = brackets.getModule("command/CommandManager"), + PathConverters = brackets.getModule("languageTools/PathConverters"); + + var SymbolKind = QuickOpen.SymbolKind; + + function convertRangePosToEditorPos(rangePos) { + return { + line: rangePos.line, + ch: rangePos.character + }; + } + + function SymbolInformation(label, fullPath, selectionRange, type, scope, isDocumentSymbolRequest) { + this.label = label; + this.fullPath = fullPath; + this.selectionRange = selectionRange; + this.type = type; + this.scope = scope; + this.isDocumentSymbolRequest = isDocumentSymbolRequest; + } + + function createList(list, isDocumentSymbolRequest) { + var newlist = []; + for (var i = 0; i < list.length; i++) { + var symbolInfo = list[i], + label = symbolInfo.name, + type = SymbolKind[symbolInfo.kind.toString()], + fullPath = null, + selectionRange = null, + scope = symbolInfo.containerName, + range = null; + + if (!isDocumentSymbolRequest) { + fullPath = PathConverters.uriToPath(symbolInfo.location.uri); + } else { + if (symbolInfo.selectionRange) { + range = symbolInfo.selectionRange; + selectionRange = { + from: convertRangePosToEditorPos(range.start), + to: convertRangePosToEditorPos(range.end) + }; + } + } + + if (!selectionRange) { + range = symbolInfo.location.range; + selectionRange = { + from: convertRangePosToEditorPos(range.start), + to: convertRangePosToEditorPos(range.end) + }; + } + + newlist.push(new SymbolInformation(label, fullPath, selectionRange, type, scope, isDocumentSymbolRequest)); + } + + return newlist; + } + + function transFormToSymbolList(query, matcher, results, isDocumentSymbolRequest) { + var list = createList(results, isDocumentSymbolRequest); + + // Filter and rank how good each match is + var filteredList = $.map(list, function (symbolInfo) { + var searchResult = matcher.match(symbolInfo.label, query); + if (searchResult) { + searchResult.symbolInfo = symbolInfo; + } + return searchResult; + }); + + // Sort based on ranking & basic alphabetical order + QuickOpen.basicMatchSort(filteredList); + + return filteredList; + } + + /** + * Provider for Document Symbols + */ + function DocumentSymbolsProvider(client) { + this.client = client; + } + + DocumentSymbolsProvider.prototype.match = function (query) { + return query.startsWith("@"); + }; + + DocumentSymbolsProvider.prototype.search = function (query, matcher) { + if (!this.client) { + return $.Deferred().reject(); + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.documentSymbolProvider) { + return $.Deferred().reject(); + } + + var editor = EditorManager.getActiveEditor(), + docPath = editor.document.file._path, + retval = $.Deferred(); + query = query.slice(1); + + this.client.requestSymbolsForDocument({ + filePath: docPath + }).done(function (results) { + var resultList = transFormToSymbolList(query, matcher, results, true); + retval.resolve(resultList); + }); + + return retval; + }; + + DocumentSymbolsProvider.prototype.itemFocus = function (selectedItem, query, explicit) { + if (!selectedItem || (query.length < 2 && !explicit)) { + return; + } + + var range = selectedItem.symbolInfo.selectionRange; + EditorManager.getCurrentFullEditor().setSelection(range.from, range.to, true); + }; + + DocumentSymbolsProvider.prototype.itemSelect = function (selectedItem, query) { + this.itemFocus(selectedItem, query, true); + }; + + DocumentSymbolsProvider.prototype.resultsFormatter = function (item, query) { + var displayName = QuickOpen.highlightMatch(item); + query = query.slice(1); + + if (item.symbolInfo.scope) { + return "
  • " + displayName + " (" + item.symbolInfo.type + ")" + "
    " + item.symbolInfo.scope + "
  • "; + } + return "
  • " + displayName + " (" + item.symbolInfo.type + ")" + "
  • "; + }; + + /** + * Provider for Project Symbols + */ + function ProjectSymbolsProvider(client) { + this.client = client; + } + + ProjectSymbolsProvider.prototype.match = function (query) { + return query.startsWith("#"); + }; + + ProjectSymbolsProvider.prototype.search = function (query, matcher) { + if (!this.client) { + return $.Deferred().reject(); + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.workspaceSymbolProvider) { + return $.Deferred().reject(); + } + + var retval = $.Deferred(); + query = query.slice(1); + + this.client.requestSymbolsForWorkspace({ + query: query + }).done(function (results) { + var resultList = transFormToSymbolList(query, matcher, results); + retval.resolve(resultList); + }); + + return retval; + }; + + ProjectSymbolsProvider.prototype.itemFocus = function (selectedItem, query, explicit) { + if (!selectedItem || (query.length < 2 && !explicit)) { + return; + } + }; + + ProjectSymbolsProvider.prototype.itemSelect = function (selectedItem, query) { + var fullPath = selectedItem.symbolInfo.fullPath, + range = selectedItem.symbolInfo.selectionRange; + + if (fullPath) { + CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, { + fullPath: fullPath + }) + .done(function () { + if (range.from) { + var editor = EditorManager.getCurrentFullEditor(); + editor.setCursorPos(range.from.line, range.from.ch, true); + } + }); + } + }; + + ProjectSymbolsProvider.prototype.resultsFormatter = function (item, query) { + var displayName = QuickOpen.highlightMatch(item); + query = query.slice(1); + + if (item.symbolInfo.scope) { + return "
  • " + displayName + " (" + item.symbolInfo.type + ")" + "
    " + item.symbolInfo.scope + "

    " + item.symbolInfo.fullPath + "
  • "; + } + return "
  • " + displayName + " (" + item.symbolInfo.type + ")" + "

    " + item.symbolInfo.fullPath + "
  • "; + }; + + exports.SymbolProviders = { + DocumentSymbolsProvider: DocumentSymbolsProvider, + ProjectSymbolsProvider: ProjectSymbolsProvider + }; +}); diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js index d2ecc966bb1..a2de7502d50 100755 --- a/src/extensions/default/PhpTooling/main.js +++ b/src/extensions/default/PhpTooling/main.js @@ -30,11 +30,13 @@ define(function (require, exports, module) { EditorManager = brackets.getModule("editor/EditorManager"), LanguageManager = brackets.getModule("language/LanguageManager"), CodeHintManager = brackets.getModule("editor/CodeHintManager"), + QuickOpen = brackets.getModule("search/QuickOpen"), ParameterHintManager = brackets.getModule("features/ParameterHintsManager"), JumpToDefManager = brackets.getModule("features/JumpToDefManager"), CodeInspection = brackets.getModule("language/CodeInspection"), DefaultProviders = brackets.getModule("languageTools/DefaultProviders"), CodeHintsProvider = require("CodeHintsProvider").CodeHintsProvider, + SymbolProviders = require("PHPSymbolProviders").SymbolProviders, DefaultEventHandlers = brackets.getModule("languageTools/DefaultEventHandlers"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), Strings = brackets.getModule("strings"), @@ -61,7 +63,9 @@ define(function (require, exports, module) { chProvider, phProvider, lProvider, - jdProvider; + jdProvider, + dSymProvider, + pSymProvider; PreferencesManager.definePreference("php", "object", phpConfig, { description: Strings.DESCRIPTION_PHP_TOOLING_CONFIGURATION @@ -102,6 +106,8 @@ define(function (require, exports, module) { phProvider = new DefaultProviders.ParameterHintsProvider(_client), lProvider = new DefaultProviders.LintingProvider(_client), jdProvider = new DefaultProviders.JumpToDefProvider(_client); + dSymProvider = new SymbolProviders.DocumentSymbolsProvider(_client); + pSymProvider = new SymbolProviders.ProjectSymbolsProvider(_client); JumpToDefManager.registerJumpToDefProvider(jdProvider, ["php"], 0); CodeHintManager.registerHintProvider(chProvider, ["php"], 0); @@ -110,6 +116,30 @@ define(function (require, exports, module) { name: "", scanFileAsync: lProvider.getInspectionResultsAsync.bind(lProvider) }); + //Attach plugin for Document Symbols + QuickOpen.addQuickOpenPlugin({ + name: "PHP Document Symbols", + label: Strings.CMD_FIND_DOCUMENT_SYMBOLS + "\u2026", + languageIds: ["php"], + search: dSymProvider.search.bind(dSymProvider), + match: dSymProvider.match.bind(dSymProvider), + itemFocus: dSymProvider.itemFocus.bind(dSymProvider), + itemSelect: dSymProvider.itemSelect.bind(dSymProvider), + resultsFormatter: dSymProvider.resultsFormatter.bind(dSymProvider) + }); + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION).setEnabled(true); + //Attach plugin for Project Symbols + QuickOpen.addQuickOpenPlugin({ + name: "PHP Project Symbols", + label: Strings.CMD_FIND_PROJECT_SYMBOLS + "\u2026", + languageIds: ["php"], + search: pSymProvider.search.bind(pSymProvider), + match: pSymProvider.match.bind(pSymProvider), + itemFocus: pSymProvider.itemFocus.bind(pSymProvider), + itemSelect: pSymProvider.itemSelect.bind(pSymProvider), + resultsFormatter: pSymProvider.resultsFormatter.bind(pSymProvider) + }); + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(true); _client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider)); } diff --git a/src/extensions/default/PhpTooling/unittest-files/test/test4.php b/src/extensions/default/PhpTooling/unittest-files/test/test4.php new file mode 100644 index 00000000000..7caa64749ed --- /dev/null +++ b/src/extensions/default/PhpTooling/unittest-files/test/test4.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/src/extensions/default/PhpTooling/unittests.js b/src/extensions/default/PhpTooling/unittests.js index 722acc5b993..3724743b5e7 100644 --- a/src/extensions/default/PhpTooling/unittests.js +++ b/src/extensions/default/PhpTooling/unittests.js @@ -27,7 +27,8 @@ define(function (require, exports, module) { var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), Strings = brackets.getModule("strings"), FileUtils = brackets.getModule("file/FileUtils"), - StringUtils = brackets.getModule("utils/StringUtils"); + StringUtils = brackets.getModule("utils/StringUtils"), + StringMatch = brackets.getModule("utils/StringMatch"); var extensionRequire, phpToolingExtension, @@ -37,11 +38,13 @@ define(function (require, exports, module) { CodeInspection, DefaultProviders, CodeHintsProvider, + SymbolProviders, EditorManager, testEditor, testFolder = FileUtils.getNativeModuleDirectoryPath(module) + "/unittest-files/", testFile1 = "test1.php", - testFile2 = "test2.php"; + testFile2 = "test2.php", + testFile4 = "test4.php"; describe("PhpTooling", function () { @@ -59,8 +62,11 @@ define(function (require, exports, module) { afterLast(function () { waitsForDone(phpToolingExtension.getClient().stop(), "stoping php server"); + testEditor = null; + testWindow = null; + brackets = null; + EditorManager = null; SpecRunnerUtils.closeTestWindow(); - testWindow = null; }); @@ -71,6 +77,7 @@ define(function (require, exports, module) { CodeInspection.toggleEnabled(true); DefaultProviders = testWindow.brackets.getModule("languageTools/DefaultProviders"); CodeHintsProvider = extensionRequire("CodeHintsProvider"); + SymbolProviders = extensionRequire("PHPSymbolProviders").SymbolProviders; }); /** @@ -316,6 +323,56 @@ define(function (require, exports, module) { }); } + /** + * Show the document/project symbols for a language type. + * + * @param {SymbolProvider} provider The symbol provider to use for the request. + * @param {string} query The query string for the request. + * @param {Array} expectedSymbols Expected results for the request. + */ + function expectSymbols(provider, query, expectedSymbols) { + var requestStatus = null; + var request, + matcher; + + runs(function () { + matcher = new StringMatch.StringMatcher(); + request = new provider(phpToolingExtension.getClient()).search(query, matcher); + request.done(function (status) { + requestStatus = status; + }); + + waitsForDone(request, "Expected Symbols did not resolve", 3000); + }); + + if (expectedSymbols === []) { + expect(requestStatus).toBe([]); + return; + } + + function matchSymbols(symbols) { + var n = symbols.length > 4 ? 4 : symbols.length, + i; + + for (i = 0; i < n; i++) { + var symbolInfo = symbols[i].symbolInfo; + expect(symbolInfo.label).toBe(expectedSymbols[i].label); + expect(symbolInfo.type).toBe(expectedSymbols[i].type); + expect(symbolInfo.scope).toBe(expectedSymbols[i].scope); + + if (expectedSymbols[i].fullPath === null) { + expect(symbolInfo.fullPath).toBe(null); + } else { + expect(symbolInfo.fullPath.includes(expectedSymbols[i].fullPath)).toBe(true); + } + } + + } + runs(function() { + matchSymbols(requestStatus); + }); + } + /** * Trigger a jump to definition, and verify that the editor jumped to * the expected location. The new location is the variable definition @@ -495,5 +552,72 @@ define(function (require, exports, module) { editorJumped({line: 4, ch: 0, file: "test3.php"}); }); }); + + it("should fetch document symbols for a given file", function () { + waitsForDone(SpecRunnerUtils.openProjectFiles([testFile4]), "open test file: " + testFile4); + runs(function () { + var provider = SymbolProviders.DocumentSymbolsProvider, + query = "@", + expectedSymbols = [ + { + "label": "constantValue", + "fullPath": null, + "type": "Constant", + "scope": "MyClass" + }, + { + "label": "MyClass", + "fullPath": null, + "type": "Class", + "scope": "" + }, + { + "label": "publicFunction", + "fullPath": null, + "type": "Method", + "scope": "MyClass" + }, + { + "label": "publicValue", + "fullPath": null, + "type": "Property", + "scope": "MyClass" + } + ]; + expectSymbols(provider, query, expectedSymbols); + }); + }); + + it("should fetch no document symbols for a given file", function () { + waitsForDone(SpecRunnerUtils.openProjectFiles([testFile1]), "open test file: " + testFile1); + runs(function () { + var provider = SymbolProviders.DocumentSymbolsProvider, + query = "@", + expectedSymbols = []; + expectSymbols(provider, query, expectedSymbols); + }); + }); + + it("should fetch project symbols for a given file", function () { + runs(function () { + var provider = SymbolProviders.ProjectSymbolsProvider, + query = "#as", + expectedSymbols = [ + { + "label": "MyClass", + "fullPath": "test4.php", + "type": "Class", + "scope": "" + }, + { + "label": "TestCase", + "fullPath": "test2.php", + "type": "Class", + "scope": "test" + } + ]; + expectSymbols(provider, query, expectedSymbols); + }); + }); }); }); diff --git a/src/languageTools/LanguageClientWrapper.js b/src/languageTools/LanguageClientWrapper.js index 9cd7a5a42b1..edd9a6b9602 100644 --- a/src/languageTools/LanguageClientWrapper.js +++ b/src/languageTools/LanguageClientWrapper.js @@ -110,7 +110,7 @@ define(function (require, exports, module) { } case ToolingInfo.FEATURES.PROJECT_SYMBOLS: { - if (params && params.query && typeof params.query === "string") { + if (hasValidProp(params, "query") && typeof params.query === "string") { validatedParams = params; } break; diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index a3afc09921b..f4874acbd6c 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -419,6 +419,7 @@ define({ "CMD_QUICK_OPEN" : "Quick Open", "CMD_GOTO_LINE" : "Go to Line", "CMD_GOTO_DEFINITION" : "Quick Find Definition", + "CMD_GOTO_DEFINITION_PROJECT" : "Quick Find Definition in Project", "CMD_GOTO_FIRST_PROBLEM" : "Go to First Problem", "CMD_TOGGLE_QUICK_EDIT" : "Quick Edit", "CMD_TOGGLE_QUICK_DOCS" : "Quick Docs", @@ -888,5 +889,7 @@ define({ "OPEN_PREFERENNCES" : "Open Preferences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools", + "CMD_FIND_DOCUMENT_SYMBOLS" : "Find Document Symbols", + "CMD_FIND_PROJECT_SYMBOLS" : "Find Project Symbols" }); diff --git a/src/search/QuickOpen.js b/src/search/QuickOpen.js index 5c9f105292f..98336ac40a0 100644 --- a/src/search/QuickOpen.js +++ b/src/search/QuickOpen.js @@ -44,8 +44,40 @@ define(function (require, exports, module) { LanguageManager = require("language/LanguageManager"), ModalBar = require("widgets/ModalBar").ModalBar, QuickSearchField = require("search/QuickSearchField").QuickSearchField, - StringMatch = require("utils/StringMatch"); - + StringMatch = require("utils/StringMatch"), + ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler; + + var _providerRegistrationHandler = new ProviderRegistrationHandler(), + _registerQuickOpenProvider = _providerRegistrationHandler.registerProvider.bind(_providerRegistrationHandler); + + var SymbolKind = { + "1": "File", + "2": "Module", + "3": "Namespace", + "4": "Package", + "5": "Class", + "6": "Method", + "7": "Property", + "8": "Field", + "9": "Constructor", + "10": "Enum", + "11": "Interface", + "12": "Function", + "13": "Variable", + "14": "Constant", + "15": "String", + "16": "Number", + "17": "Boolean", + "18": "Array", + "19": "Object", + "20": "Key", + "21": "Null", + "22": "EnumMember", + "23": "Struct", + "24": "Event", + "25": "Operator", + "26": "TypeParameter" + }; /** * The regular expression to check the cursor position @@ -53,12 +85,6 @@ define(function (require, exports, module) { */ var CURSOR_POS_EXP = new RegExp(":([^,]+)?(,(.+)?)?"); - /** - * List of plugins - * @type {Array.} - */ - var plugins = []; - /** * Current plugin * @type {QuickOpenPlugin} @@ -77,6 +103,22 @@ define(function (require, exports, module) { */ var _curDialog; + /** + * Helper function to get the plugins based on the type of the current document. + * @private + * @returns {Array} Returns the plugings based on the languageId of the current document. + */ + function _getPluginsForCurrentContext() { + var curDoc = DocumentManager.getCurrentDocument(); + + if (curDoc) { + var languageId = curDoc.getLanguage().getId(); + return _providerRegistrationHandler.getProvidersForLanguageId(languageId); + } + + return _providerRegistrationHandler.getProvidersForLanguageId(); //plugins registered for all + } + /** * Defines API for new QuickOpen plug-ins */ @@ -132,18 +174,22 @@ define(function (require, exports, module) { * cancels Quick Open (via Esc), those changes are automatically reverted. */ function addQuickOpenPlugin(pluginDef) { - plugins.push(new QuickOpenPlugin( - pluginDef.name, - pluginDef.languageIds, - pluginDef.done, - pluginDef.search, - pluginDef.match, - pluginDef.itemFocus, - pluginDef.itemSelect, - pluginDef.resultsFormatter, - pluginDef.matcherOptions, - pluginDef.label - )); + var quickOpenProvider = new QuickOpenPlugin( + pluginDef.name, + pluginDef.languageIds, + pluginDef.done, + pluginDef.search, + pluginDef.match, + pluginDef.itemFocus, + pluginDef.itemSelect, + pluginDef.resultsFormatter, + pluginDef.matcherOptions, + pluginDef.label + ), + providerLanguageIds = pluginDef.languageIds.length ? pluginDef.languageIds : ["all"], + providerPriority = pluginDef.priority || 0; + + _registerQuickOpenProvider(quickOpenProvider, providerLanguageIds, providerPriority); } /** @@ -350,9 +396,10 @@ define(function (require, exports, module) { this.closePromise = modalBarClosePromise; this.isOpen = false; - var i; + var i, + plugins = _getPluginsForCurrentContext(); for (i = 0; i < plugins.length; i++) { - var plugin = plugins[i]; + var plugin = plugins[i].provider; if (plugin.done) { plugin.done(); } @@ -455,17 +502,11 @@ define(function (require, exports, module) { return { error: null }; } - // Try to invoke a search plugin - var curDoc = DocumentManager.getCurrentDocument(), languageId; - if (curDoc) { - languageId = curDoc.getLanguage().getId(); - } - - var i; + var i, + plugins = _getPluginsForCurrentContext(); for (i = 0; i < plugins.length; i++) { - var plugin = plugins[i]; - var languageIdMatch = plugin.languageIds.length === 0 || plugin.languageIds.indexOf(languageId) !== -1; - if (languageIdMatch && plugin.match(query)) { + var plugin = plugins[i].provider; + if(plugin.match(query)) { currentPlugin = plugin; // Look up the StringMatcher for this plugin. @@ -613,6 +654,9 @@ define(function (require, exports, module) { case "@": dialogLabel = Strings.CMD_GOTO_DEFINITION + "\u2026"; break; + case "#": + dialogLabel = Strings.CMD_GOTO_DEFINITION_PROJECT + "\u2026"; + break; default: dialogLabel = ""; break; @@ -732,18 +776,89 @@ define(function (require, exports, module) { } } + function doDefinitionSearchInProject() { + if (DocumentManager.getCurrentDocument()) { + beginSearch("#", getCurrentEditorSelectedText()); + } + } + + function _canHandleTrigger(trigger, plugins) { + var retval = false; + + plugins.some(function (plugin, index) { + var provider = plugin.provider; + if (provider.match(trigger)) { + retval = true; + return true; + } + }); + + return retval; + } + + function _setMenuItemStateForLanguage(languageId) { + var plugins = _providerRegistrationHandler.getProvidersForLanguageId(languageId); + if (_canHandleTrigger("@", plugins)) { + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION).setEnabled(true); + } else { + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION).setEnabled(false); + } + + if (_canHandleTrigger("#", plugins)) { + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(true); + } else { + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(false); + } + } + // Listen for a change of project to invalidate our file list ProjectManager.on("projectOpen", function () { fileList = null; }); + MainViewManager.on("currentFileChange", function (event, newFile, newPaneId, oldFile, oldPaneId) { + if (!newFile) { + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION).setEnabled(false); + CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(false); + return; + } + + var newFilePath = newFile.fullPath, + newLanguageId = LanguageManager.getLanguageForPath(newFilePath).getId(); + _setMenuItemStateForLanguage(newLanguageId); + + DocumentManager.getDocumentForPath(newFilePath) + .done(function (newDoc) { + newDoc.on("languageChanged.quickFindDefinition", function () { + var changedLanguageId = LanguageManager.getLanguageForPath(newDoc.file.fullPath).getId(); + _setMenuItemStateForLanguage(changedLanguageId); + }); + }).fail(function (err) { + console.error(err); + }); + + if (!oldFile) { + return; + } + + var oldFilePath = oldFile.fullPath; + DocumentManager.getDocumentForPath(oldFilePath) + .done(function (oldDoc) { + oldDoc.off("languageChanged.quickFindDefinition"); + }).fail(function (err) { + console.error(err); + }); + }); + CommandManager.register(Strings.CMD_QUICK_OPEN, Commands.NAVIGATE_QUICK_OPEN, doFileSearch); CommandManager.register(Strings.CMD_GOTO_DEFINITION, Commands.NAVIGATE_GOTO_DEFINITION, doDefinitionSearch); + CommandManager.register(Strings.CMD_GOTO_DEFINITION_PROJECT, Commands.NAVIGATE_GOTO_DEFINITION_PROJECT, doDefinitionSearchInProject); CommandManager.register(Strings.CMD_GOTO_LINE, Commands.NAVIGATE_GOTO_LINE, doGotoLine); exports.beginSearch = beginSearch; exports.addQuickOpenPlugin = addQuickOpenPlugin; exports.highlightMatch = highlightMatch; + exports.SymbolKind = SymbolKind; // Convenience exports for functions that most QuickOpen plugins would need. exports.stringMatch = StringMatch.stringMatch; From 7c5a7fcba1b9a115a4a88baa8104ec51832e2e97 Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Sun, 14 Apr 2019 16:54:56 +0000 Subject: [PATCH 075/149] Updated by ALF automation. --- src/nls/fr/strings.js | 5 ++++- src/nls/ja/strings.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index f69da6a973b..939920bd613 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -419,6 +419,7 @@ define({ "CMD_QUICK_OPEN": "Ouverture rapide", "CMD_GOTO_LINE": "Atteindre la ligne", "CMD_GOTO_DEFINITION": "Accès rapide à la définition", + "CMD_GOTO_DEFINITION_PROJECT": "Quick Find Definition in Project", "CMD_GOTO_FIRST_PROBLEM": "Accéder au premier problème", "CMD_TOGGLE_QUICK_EDIT": "Édition rapide", "CMD_TOGGLE_QUICK_DOCS": "Documentation rapide", @@ -888,5 +889,7 @@ define({ "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools", + "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", + "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 4e50b038526..0f41e1e0d91 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -419,6 +419,7 @@ define({ "CMD_QUICK_OPEN": "クイックオープン", "CMD_GOTO_LINE": "行に移動", "CMD_GOTO_DEFINITION": "定義をクイック検索", + "CMD_GOTO_DEFINITION_PROJECT": "Quick Find Definition in Project", "CMD_GOTO_FIRST_PROBLEM": "最初の問題に移動", "CMD_TOGGLE_QUICK_EDIT": "クイック編集", "CMD_TOGGLE_QUICK_DOCS": "クイックドキュメント", @@ -888,5 +889,7 @@ define({ "OPEN_PREFERENNCES": "環境設定を開く", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools", + "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", + "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols" }); From 317992fa672c03f2096b16a9783c7e4c72f0de1e Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Mon, 15 Apr 2019 16:12:35 +0530 Subject: [PATCH 076/149] LSP Find References Feature (#14693) * LSP Find References Feature * Addressed bugs * Addressed review comments * Addressed review comments * Changes to send data only once per 24 hours * Adding Unit Test cases and supporting files * changed string * fixed review comments * Addressed review comments * Changed Shortcut to Ctrl-M * Changed the Shortcut to Shift-F12 --- src/base-config/keyboard.json | 3 + src/brackets.js | 1 + src/command/Commands.js | 1 + src/command/DefaultMenus.js | 2 + src/extensions/default/PhpTooling/main.js | 6 +- .../PhpTooling/unittest-files/test/test2.php | 17 ++ .../PhpTooling/unittest-files/test/test3.php | 4 +- .../default/PhpTooling/unittests.js | 140 +++++++++++++++++ src/features/FindReferencesManager.js | 148 ++++++++++++++++++ src/languageTools/DefaultProviders.js | 89 +++++++++++ src/nls/root/strings.js | 6 + src/search/SearchResultsView.js | 14 +- 12 files changed, 427 insertions(+), 4 deletions(-) create mode 100644 src/features/FindReferencesManager.js diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json index 07611fea90c..5d7ea339515 100644 --- a/src/base-config/keyboard.json +++ b/src/base-config/keyboard.json @@ -162,6 +162,9 @@ "cmd.findInFiles": [ "Ctrl-Shift-F" ], + "cmd.findAllReferences": [ + "Shift-F12" + ], "cmd.replaceInFiles": [ { "key": "Ctrl-Shift-H" diff --git a/src/brackets.js b/src/brackets.js index 4365249ef05..a9935b1589b 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -141,6 +141,7 @@ define(function (require, exports, module) { //load language features require("features/ParameterHintsManager"); require("features/JumpToDefManager"); + require("features/FindReferencesManager"); // Load modules that self-register and just need to get included in the main project require("command/DefaultMenus"); diff --git a/src/command/Commands.js b/src/command/Commands.js index e5811dd229c..d0f0087676b 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -97,6 +97,7 @@ define(function (require, exports, module) { exports.CMD_REPLACE = "cmd.replace"; // FindReplace.js _replace() exports.CMD_REPLACE_IN_FILES = "cmd.replaceInFiles"; // FindInFilesUI.js _showReplaceBar() exports.CMD_REPLACE_IN_SUBTREE = "cmd.replaceInSubtree"; // FindInFilesUI.js _showReplaceBarForSubtree() + exports.CMD_FIND_ALL_REFERENCES = "cmd.findAllReferences"; // findReferencesManager.js _openReferencesPanel() // VIEW exports.CMD_THEMES_OPEN_SETTINGS = "view.themesOpenSetting"; // MenuCommands.js Settings.open() diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js index 912d9eaa306..86e6776d1fd 100644 --- a/src/command/DefaultMenus.js +++ b/src/command/DefaultMenus.js @@ -136,6 +136,7 @@ define(function (require, exports, module) { menu.addMenuItem(Commands.CMD_SKIP_CURRENT_MATCH); menu.addMenuDivider(); menu.addMenuItem(Commands.CMD_FIND_IN_FILES); + menu.addMenuItem(Commands.CMD_FIND_ALL_REFERENCES); menu.addMenuDivider(); menu.addMenuItem(Commands.CMD_REPLACE); menu.addMenuItem(Commands.CMD_REPLACE_IN_FILES); @@ -283,6 +284,7 @@ define(function (require, exports, module) { // editor_cmenu.addMenuItem(Commands.NAVIGATE_JUMPTO_DEFINITION); editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_EDIT); editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_DOCS); + editor_cmenu.addMenuItem(Commands.CMD_FIND_ALL_REFERENCES); editor_cmenu.addMenuDivider(); editor_cmenu.addMenuItem(Commands.EDIT_CUT); editor_cmenu.addMenuItem(Commands.EDIT_COPY); diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js index a2de7502d50..afd2059d084 100755 --- a/src/extensions/default/PhpTooling/main.js +++ b/src/extensions/default/PhpTooling/main.js @@ -33,6 +33,7 @@ define(function (require, exports, module) { QuickOpen = brackets.getModule("search/QuickOpen"), ParameterHintManager = brackets.getModule("features/ParameterHintsManager"), JumpToDefManager = brackets.getModule("features/JumpToDefManager"), + FindReferencesManager = brackets.getModule("features/FindReferencesManager"), CodeInspection = brackets.getModule("language/CodeInspection"), DefaultProviders = brackets.getModule("languageTools/DefaultProviders"), CodeHintsProvider = require("CodeHintsProvider").CodeHintsProvider, @@ -65,7 +66,8 @@ define(function (require, exports, module) { lProvider, jdProvider, dSymProvider, - pSymProvider; + pSymProvider, + refProvider; PreferencesManager.definePreference("php", "object", phpConfig, { description: Strings.DESCRIPTION_PHP_TOOLING_CONFIGURATION @@ -108,10 +110,12 @@ define(function (require, exports, module) { jdProvider = new DefaultProviders.JumpToDefProvider(_client); dSymProvider = new SymbolProviders.DocumentSymbolsProvider(_client); pSymProvider = new SymbolProviders.ProjectSymbolsProvider(_client); + refProvider = new DefaultProviders.ReferencesProvider(_client); JumpToDefManager.registerJumpToDefProvider(jdProvider, ["php"], 0); CodeHintManager.registerHintProvider(chProvider, ["php"], 0); ParameterHintManager.registerHintProvider(phProvider, ["php"], 0); + FindReferencesManager.registerFindReferencesProvider(refProvider, ["php"], 0); CodeInspection.register(["php"], { name: "", scanFileAsync: lProvider.getInspectionResultsAsync.bind(lProvider) diff --git a/src/extensions/default/PhpTooling/unittest-files/test/test2.php b/src/extensions/default/PhpTooling/unittest-files/test/test2.php index 16651124644..5080a3f3158 100644 --- a/src/extensions/default/PhpTooling/unittest-files/test/test2.php +++ b/src/extensions/default/PhpTooling/unittest-files/test/test2.php @@ -26,5 +26,22 @@ function watchparameterhint() { $A11() fopen("",) watchparameterhint() + + +function watchReferences() { + echo "Hello World!"; +} + +watchReferences(); + +watchReferences(); + + +function ReferencesInMultipleFile() { + echo "Hello World!"; +} + +ReferencesInMultipleFile(); +ReferencesInMultipleFile(); ?> diff --git a/src/extensions/default/PhpTooling/unittest-files/test/test3.php b/src/extensions/default/PhpTooling/unittest-files/test/test3.php index 70981b54d1f..7dc1d365bd2 100644 --- a/src/extensions/default/PhpTooling/unittest-files/test/test3.php +++ b/src/extensions/default/PhpTooling/unittest-files/test/test3.php @@ -7,4 +7,6 @@ class testA protected $B = [ 'A1', 'A2' ]; -} \ No newline at end of file +} + +ReferencesInMultipleFile(); \ No newline at end of file diff --git a/src/extensions/default/PhpTooling/unittests.js b/src/extensions/default/PhpTooling/unittests.js index 3724743b5e7..53b915227da 100644 --- a/src/extensions/default/PhpTooling/unittests.js +++ b/src/extensions/default/PhpTooling/unittests.js @@ -398,6 +398,40 @@ define(function (require, exports, module) { } + function expectReferences(referencesExpected) { + var refPromise, + results = null, + complete = false; + runs(function () { + refPromise = (new DefaultProviders.ReferencesProvider(phpToolingExtension.getClient())).getReferences(); + refPromise.done(function (resp) { + complete = true; + results = resp; + }).fail(function(){ + complete = true; + }); + }); + + waitsFor(function () { + return complete; + }, "Expected Reference Promise did not resolve", 3000); + + if(referencesExpected === null) { + expect(results).toBeNull(); + return; + } + + runs(function() { + expect(results.numFiles).toBe(referencesExpected.numFiles); + expect(results.numMatches).toBe(referencesExpected.numMatches); + expect(results.allResultsAvailable).toBe(referencesExpected.allResultsAvailable); + expect(results.results).not.toBeNull(); + for(var key in results.keys) { + expect(results.results.key).toBe(referencesExpected.results.key); + } + }); + } + /** * Check the presence of Error Prompt on Brackets Window */ @@ -533,6 +567,112 @@ define(function (require, exports, module) { }); }); + it("should not show any references", function () { + var start = { line: 6, ch: 4 }; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + expectReferences(null); + }); + }); + + it("should show reference present in single file", function () { + var start = { line: 22, ch: 18 }, + results = {}; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + results[testFolder + "test/test2.php"] = {matches: [ + { + start: {line: 27, ch: 0}, + end: {line: 27, ch: 18}, + line: "watchparameterhint()" + } + ] + }; + expectReferences({ + numFiles: 1, + numMatches: 1, + allResultsAvailable: true, + queryInfo: "watchparameterhint", + keys: [testFolder + "test/test2.php"], + results: results + }); + }); + }); + + it("should show references present in single file", function () { + var start = { line: 34, ch: 8 }, + results = {}; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + results[testFolder + "test/test2.php"] = {matches: [ + { + start: {line: 34, ch: 0}, + end: {line: 34, ch: 17}, + line: "watchReferences();" + }, + { + start: {line: 36, ch: 0}, + end: {line: 36, ch: 17}, + line: "watchReferences();" + } + ] + }; + expectReferences({ + numFiles: 1, + numMatches: 2, + allResultsAvailable: true, + queryInfo: "watchparameterhint", + keys: [testFolder + "test/test2.php"], + results: results + }); + }); + }); + + it("should show references present in multiple files", function () { + var start = { line: 39, ch: 21 }, + results = {}; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + results[testFolder + "test/test2.php"] = {matches: [ + { + start: {line: 34, ch: 0}, + end: {line: 34, ch: 26}, + line: "watchReferences();" + }, + { + start: {line: 36, ch: 0}, + end: {line: 36, ch: 26}, + line: "watchReferences();" + } + ] + }; + results[testFolder + "test/test3.php"] = {matches: [ + { + start: {line: 11, ch: 0}, + end: {line: 11, ch: 26}, + line: "watchReferences();" + } + ] + }; + expectReferences({ + numFiles: 2, + numMatches: 3, + allResultsAvailable: true, + queryInfo: "watchparameterhint", + keys: [testFolder + "test/test2.php", testFolder + "test/test3.php"], + results: results + }); + }); + }); + it("should jump to earlier defined variable", function () { var start = { line: 4, ch: 2 }; diff --git a/src/features/FindReferencesManager.js b/src/features/FindReferencesManager.js new file mode 100644 index 00000000000..3ae4c4b3cbd --- /dev/null +++ b/src/features/FindReferencesManager.js @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var AppInit = require("utils/AppInit"), + CommandManager = require("command/CommandManager"), + Commands = require("command/Commands"), + EditorManager = require("editor/EditorManager"), + ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler, + SearchResultsView = require("search/SearchResultsView").SearchResultsView, + SearchModel = require("search/SearchModel").SearchModel, + Strings = require("strings"); + + var _providerRegistrationHandler = new ProviderRegistrationHandler(), + registerFindReferencesProvider = _providerRegistrationHandler.registerProvider.bind( + _providerRegistrationHandler + ), + removeFindReferencesProvider = _providerRegistrationHandler.removeProvider.bind(_providerRegistrationHandler); + + var searchModel = new SearchModel(), + _resultsView; + + function _getReferences(provider, hostEditor, pos) { + var result = new $.Deferred(); + + if(!provider) { + return result.reject(); + } + + provider.getReferences(hostEditor, pos) + .done(function (rcvdObj) { + + searchModel.results = rcvdObj.results; + searchModel.numFiles = rcvdObj.numFiles; + searchModel.numMatches = rcvdObj.numMatches; + searchModel.allResultsAvailable = true; + searchModel.setQueryInfo({query: rcvdObj.queryInfo, caseSensitive: true, isRegExp: false}); + result.resolve(); + }).fail(function (){ + result.reject(); + }); + return result.promise(); + + } + + function _openReferencesPanel() { + var editor = EditorManager.getActiveEditor(), + pos = editor ? editor.getCursorPos() : null, + referencesPromise, + result = new $.Deferred(), + errorMsg = Strings.REFERENCES_NO_RESULTS, + referencesProvider; + + var language = editor.getLanguageForSelection(), + enabledProviders = _providerRegistrationHandler.getProvidersForLanguageId(language.getId()); + + enabledProviders.some(function (item, index) { + if (item.provider.hasReferences(editor)) { + referencesProvider = item.provider; + return true; + } + }); + + referencesPromise = _getReferences(referencesProvider, editor, pos); + + // If one of them will provide a widget, show it inline once ready + if (referencesPromise) { + referencesPromise.done(function () { + if(_resultsView) { + _resultsView.open(); + } + }).fail(function () { + if(_resultsView) { + _resultsView.close(); + } + editor.displayErrorMessageAtCursor(errorMsg); + result.reject(); + }); + } else { + if(_resultsView) { + _resultsView.close(); + } + editor.displayErrorMessageAtCursor(errorMsg); + result.reject(); + } + + return result.promise(); + } + + /** + * @private + * Clears any previous search information, removing update listeners and clearing the model. + */ + function _clearSearch() { + searchModel.clear(); + } + + AppInit.htmlReady(function () { + _resultsView = new SearchResultsView( + searchModel, + "reference-in-files-results", + "reference-in-files.results", + "reference" + ); + if(_resultsView) { + _resultsView + .on("close", function () { + _clearSearch(); + }) + .on("getNextPage", function () { + if (searchModel.hasResults()) { + _resultsView.showNextPage(); + } + }) + .on("getLastPage", function () { + if (searchModel.hasResults()) { + _resultsView.showLastPage(); + } + }); + } + }); + CommandManager.register(Strings.FIND_ALL_REFERENCES, Commands.CMD_FIND_ALL_REFERENCES, _openReferencesPanel); + + exports.registerFindReferencesProvider = registerFindReferencesProvider; + exports.removeFindReferencesProvider = removeFindReferencesProvider; +}); diff --git a/src/languageTools/DefaultProviders.js b/src/languageTools/DefaultProviders.js index fbeb67e36ec..47761ac0aa9 100644 --- a/src/languageTools/DefaultProviders.js +++ b/src/languageTools/DefaultProviders.js @@ -30,6 +30,7 @@ define(function (require, exports, module) { var _ = brackets.getModule("thirdparty/lodash"); var EditorManager = require('editor/EditorManager'), + DocumentManager = require('document/DocumentManager'), ExtensionUtils = require("utils/ExtensionUtils"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), @@ -404,8 +405,96 @@ define(function (require, exports, module) { return this._results.get(filePath); }; + function serverRespToSearchModelFormat(msgObj) { + var referenceModel = {}, + result = $.Deferred(); + + if(!(msgObj && msgObj.length && msgObj.cursorPos)) { + return result.reject(); + } + referenceModel.results = {}; + referenceModel.numFiles = 0; + var fulfilled = 0; + msgObj.forEach((element, i) => { + var filePath = PathConverters.uriToPath(element.uri); + DocumentManager.getDocumentForPath(filePath) + .done(function(doc) { + var startRange = {line: element.range.start.line, ch: element.range.start.character}; + var endRange = {line: element.range.end.line, ch: element.range.end.character}; + var match = { + start: startRange, + end: endRange, + highlightOffset: 0, + line: doc.getLine(element.range.start.line) + }; + if(!referenceModel.results[filePath]) { + referenceModel.numFiles = referenceModel.numFiles + 1; + referenceModel.results[filePath] = {"matches": []}; + } + if(!referenceModel.queryInfo || msgObj.cursorPos.line === startRange.line) { + referenceModel.queryInfo = doc.getRange(startRange, endRange); + } + referenceModel.results[filePath]["matches"].push(match); + }).always(function() { + fulfilled++; + if(fulfilled === msgObj.length) { + referenceModel.numMatches = msgObj.length; + referenceModel.allResultsAvailable = true; + result.resolve(referenceModel); + } + }); + }); + return result.promise(); + } + + function ReferencesProvider(client) { + this.client = client; + } + + ReferencesProvider.prototype.hasReferences = function() { + if (!this.client) { + return false; + } + + var serverCapabilities = this.client.getServerCapabilities(); + if (!serverCapabilities || !serverCapabilities.referencesProvider) { + return false; + } + + return true; + }; + + ReferencesProvider.prototype.getReferences = function(hostEditor, curPos) { + var editor = hostEditor || EditorManager.getActiveEditor(), + pos = curPos || editor ? editor.getCursorPos() : null, + docPath = editor.document.file._path, + result = $.Deferred(); + + if (this.client) { + this.client.findReferences({ + filePath: docPath, + cursorPos: pos + }).done(function(msgObj){ + if(msgObj && msgObj.length) { + msgObj.cursorPos = pos; + serverRespToSearchModelFormat(msgObj) + .done(result.resolve) + .fail(result.reject); + } else { + result.reject(); + } + }).fail(function(){ + result.reject(); + }); + return result.promise(); + } + return result.reject(); + }; + exports.CodeHintsProvider = CodeHintsProvider; exports.ParameterHintsProvider = ParameterHintsProvider; exports.JumpToDefProvider = JumpToDefProvider; exports.LintingProvider = LintingProvider; + exports.ReferencesProvider = ReferencesProvider; + exports.serverRespToSearchModelFormat = serverRespToSearchModelFormat; }); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index f4874acbd6c..2668105fa05 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -890,6 +890,12 @@ define({ //Strings for LanguageTools Preferences "LANGUAGE_TOOLS_PREFERENCES" : "Preferences for Language Tools", + + "FIND_ALL_REFERENCES" : "Find All References", + "REFERENCES_IN_FILES" : "references", + "REFERENCE_IN_FILES" : "reference", + "REFERENCES_NO_RESULTS" : "No References available for current cursor position", + "CMD_FIND_DOCUMENT_SYMBOLS" : "Find Document Symbols", "CMD_FIND_PROJECT_SYMBOLS" : "Find Project Symbols" }); diff --git a/src/search/SearchResultsView.js b/src/search/SearchResultsView.js index 13eb92790a0..1822926a26b 100644 --- a/src/search/SearchResultsView.js +++ b/src/search/SearchResultsView.js @@ -72,14 +72,16 @@ define(function (require, exports, module) { * @param {SearchModel} model The model that this view is showing. * @param {string} panelID The CSS ID to use for the panel. * @param {string} panelName The name to use for the panel, as passed to WorkspaceManager.createBottomPanel(). + * @param {string} type type to identify if it is reference search or string match serach */ - function SearchResultsView(model, panelID, panelName) { + function SearchResultsView(model, panelID, panelName, type) { var panelHtml = Mustache.render(searchPanelTemplate, {panelID: panelID}); this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100); this._$summary = this._panel.$panel.find(".title"); this._$table = this._panel.$panel.find(".table-container"); this._model = model; + this._searchResultsType = type; } EventDispatcher.makeEventDispatcher(SearchResultsView.prototype); @@ -116,6 +118,9 @@ define(function (require, exports, module) { /** @type {number} The ID we use for timeouts when handling model changes. */ SearchResultsView.prototype._timeoutID = null; + /** @type {string} The Id we use to check if it is reference search or match search */ + SearchResultsView.prototype._searchResultsType = null; + /** * @private * Handles when model changes. Updates the view, buffering changes if necessary so as not to churn too much. @@ -344,9 +349,14 @@ define(function (require, exports, module) { SearchResultsView.prototype._showSummary = function () { var count = this._model.countFilesMatches(), lastIndex = this._getLastIndex(count.matches), + typeStr = (count.matches > 1) ? Strings.FIND_IN_FILES_MATCHES : Strings.FIND_IN_FILES_MATCH, filesStr, summary; + if(this._searchResultsType === "reference") { + typeStr = (count.matches > 1) ? Strings.REFERENCES_IN_FILES : Strings.REFERENCE_IN_FILES; + } + filesStr = StringUtils.format( Strings.FIND_NUM_FILES, count.files, @@ -358,7 +368,7 @@ define(function (require, exports, module) { Strings.FIND_TITLE_SUMMARY, this._model.exceedsMaximum ? Strings.FIND_IN_FILES_MORE_THAN : "", String(count.matches), - (count.matches > 1) ? Strings.FIND_IN_FILES_MATCHES : Strings.FIND_IN_FILES_MATCH, + typeStr, filesStr ); From 4f6954c2c1dc78e3b38e97efca44899fed25bad9 Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Mon, 15 Apr 2019 16:55:57 +0000 Subject: [PATCH 077/149] Updated by ALF automation. --- src/nls/fr/strings.js | 6 ++++++ src/nls/ja/strings.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 939920bd613..e3991ee7f97 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -890,6 +890,12 @@ define({ //Strings for LanguageTools Preferences "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools", + + "FIND_ALL_REFERENCES": "Find All References", + "REFERENCES_IN_FILES": "references", + "REFERENCE_IN_FILES": "référence", + "REFERENCES_NO_RESULTS": "No References available for current cursor position", + "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 0f41e1e0d91..647eecb5a7b 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -890,6 +890,12 @@ define({ //Strings for LanguageTools Preferences "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools", + + "FIND_ALL_REFERENCES": "Find All References", + "REFERENCES_IN_FILES": "references", + "REFERENCE_IN_FILES": "参照", + "REFERENCES_NO_RESULTS": "No References available for current cursor position", + "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols" }); From 98a1e71757a45ad07237e1dc28b94d7e54008817 Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Wed, 17 Apr 2019 16:55:32 +0000 Subject: [PATCH 078/149] Updated by ALF automation. --- src/nls/fr/strings.js | 14 +++++++------- src/nls/ja/strings.js | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index e3991ee7f97..50c650e88b7 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -419,7 +419,7 @@ define({ "CMD_QUICK_OPEN": "Ouverture rapide", "CMD_GOTO_LINE": "Atteindre la ligne", "CMD_GOTO_DEFINITION": "Accès rapide à la définition", - "CMD_GOTO_DEFINITION_PROJECT": "Quick Find Definition in Project", + "CMD_GOTO_DEFINITION_PROJECT": "Accès rapide à la définition dans le projet", "CMD_GOTO_FIRST_PROBLEM": "Accéder au premier problème", "CMD_TOGGLE_QUICK_EDIT": "Édition rapide", "CMD_TOGGLE_QUICK_DOCS": "Documentation rapide", @@ -889,13 +889,13 @@ define({ "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools", + "LANGUAGE_TOOLS_PREFERENCES": "Préférences pour Language Tools", - "FIND_ALL_REFERENCES": "Find All References", - "REFERENCES_IN_FILES": "references", + "FIND_ALL_REFERENCES": "Rechercher toutes les références", + "REFERENCES_IN_FILES": "références", "REFERENCE_IN_FILES": "référence", - "REFERENCES_NO_RESULTS": "No References available for current cursor position", + "REFERENCES_NO_RESULTS": "Références non disponibles pour la position actuelle du curseur", - "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", - "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols" + "CMD_FIND_DOCUMENT_SYMBOLS": "Rechercher des symboles de document", + "CMD_FIND_PROJECT_SYMBOLS": "Rechercher des symboles de projet" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 647eecb5a7b..09e0f3a5a16 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -419,7 +419,7 @@ define({ "CMD_QUICK_OPEN": "クイックオープン", "CMD_GOTO_LINE": "行に移動", "CMD_GOTO_DEFINITION": "定義をクイック検索", - "CMD_GOTO_DEFINITION_PROJECT": "Quick Find Definition in Project", + "CMD_GOTO_DEFINITION_PROJECT": "プロジェクトで定義をクイック検索", "CMD_GOTO_FIRST_PROBLEM": "最初の問題に移動", "CMD_TOGGLE_QUICK_EDIT": "クイック編集", "CMD_TOGGLE_QUICK_DOCS": "クイックドキュメント", @@ -889,13 +889,13 @@ define({ "OPEN_PREFERENNCES": "環境設定を開く", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools", + "LANGUAGE_TOOLS_PREFERENCES": "言語ツールの設定", - "FIND_ALL_REFERENCES": "Find All References", - "REFERENCES_IN_FILES": "references", + "FIND_ALL_REFERENCES": "すべての参照を検索", + "REFERENCES_IN_FILES": "参照", "REFERENCE_IN_FILES": "参照", - "REFERENCES_NO_RESULTS": "No References available for current cursor position", + "REFERENCES_NO_RESULTS": "現在のカーソル位置で利用可能な参照はありません", - "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", - "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols" + "CMD_FIND_DOCUMENT_SYMBOLS": "ドキュメント記号を検索", + "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索" }); From 469f4e1c36b3590309707aed6cef99bac694b34e Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Mon, 22 Apr 2019 14:12:08 +0530 Subject: [PATCH 079/149] Fixed Find References Issue in release build (#14705) * Fixed Find References Issue in release build * disabling menu for non php file * Added code to disable Find All References Menu for non php file * corrected indentation --- src/brackets.js | 4 +- src/extensions/default/PhpTooling/main.js | 1 + src/features/FindReferencesManager.js | 62 +++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/brackets.js b/src/brackets.js index a9935b1589b..27c543879c9 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -141,7 +141,6 @@ define(function (require, exports, module) { //load language features require("features/ParameterHintsManager"); require("features/JumpToDefManager"); - require("features/FindReferencesManager"); // Load modules that self-register and just need to get included in the main project require("command/DefaultMenus"); @@ -156,6 +155,9 @@ define(function (require, exports, module) { require("search/FindInFilesUI"); require("search/FindReplace"); + //Load find References Feature Manager + require("features/FindReferencesManager"); + //Load common JS module require("JSUtils/Session"); require("JSUtils/ScopeManager"); diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js index afd2059d084..8377c62b72b 100755 --- a/src/extensions/default/PhpTooling/main.js +++ b/src/extensions/default/PhpTooling/main.js @@ -116,6 +116,7 @@ define(function (require, exports, module) { CodeHintManager.registerHintProvider(chProvider, ["php"], 0); ParameterHintManager.registerHintProvider(phProvider, ["php"], 0); FindReferencesManager.registerFindReferencesProvider(refProvider, ["php"], 0); + FindReferencesManager.setMenuItemStateForLanguage(); CodeInspection.register(["php"], { name: "", scanFileAsync: lProvider.getInspectionResultsAsync.bind(lProvider) diff --git a/src/features/FindReferencesManager.js b/src/features/FindReferencesManager.js index 3ae4c4b3cbd..50b8cdfe52e 100644 --- a/src/features/FindReferencesManager.js +++ b/src/features/FindReferencesManager.js @@ -26,8 +26,12 @@ define(function (require, exports, module) { var AppInit = require("utils/AppInit"), CommandManager = require("command/CommandManager"), + MainViewManager = require("view/MainViewManager"), + LanguageManager = require("language/LanguageManager"), + DocumentManager = require("document/DocumentManager"), Commands = require("command/Commands"), EditorManager = require("editor/EditorManager"), + ProjectManager = require("project/ProjectManager"), ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler, SearchResultsView = require("search/SearchResultsView").SearchResultsView, SearchModel = require("search/SearchModel").SearchModel, @@ -117,6 +121,58 @@ define(function (require, exports, module) { searchModel.clear(); } + function setMenuItemStateForLanguage(languageId) { + CommandManager.get(Commands.CMD_FIND_ALL_REFERENCES).setEnabled(false); + if (!languageId) { + var editor = EditorManager.getActiveEditor(); + if (editor) { + languageId = LanguageManager.getLanguageForPath(editor.document.file._path).getId(); + } + } + var enabledProviders = _providerRegistrationHandler.getProvidersForLanguageId(languageId), + referencesProvider; + + enabledProviders.some(function (item, index) { + if (item.provider.hasReferences()) { + referencesProvider = item.provider; + return true; + } + }); + if (referencesProvider) { + CommandManager.get(Commands.CMD_FIND_ALL_REFERENCES).setEnabled(true); + } + + } + + MainViewManager.on("currentFileChange", function (event, newFile, newPaneId, oldFile, oldPaneId) { + if (!newFile) { + CommandManager.get(Commands.CMD_FIND_ALL_REFERENCES).setEnabled(false); + return; + } + + var newFilePath = newFile.fullPath, + newLanguageId = LanguageManager.getLanguageForPath(newFilePath).getId(); + setMenuItemStateForLanguage(newLanguageId); + + DocumentManager.getDocumentForPath(newFilePath) + .done(function (newDoc) { + newDoc.on("languageChanged.reference-in-files", function () { + var changedLanguageId = LanguageManager.getLanguageForPath(newDoc.file.fullPath).getId(); + setMenuItemStateForLanguage(changedLanguageId); + }); + }); + + if (!oldFile) { + return; + } + + var oldFilePath = oldFile.fullPath; + DocumentManager.getDocumentForPath(oldFilePath) + .done(function (oldDoc) { + oldDoc.off("languageChanged.reference-in-files"); + }); + }); + AppInit.htmlReady(function () { _resultsView = new SearchResultsView( searchModel, @@ -141,8 +197,14 @@ define(function (require, exports, module) { }); } }); + + // Initialize: register listeners + ProjectManager.on("beforeProjectClose", function () { if (_resultsView) { _resultsView.close(); } }); + CommandManager.register(Strings.FIND_ALL_REFERENCES, Commands.CMD_FIND_ALL_REFERENCES, _openReferencesPanel); + CommandManager.get(Commands.CMD_FIND_ALL_REFERENCES).setEnabled(false); exports.registerFindReferencesProvider = registerFindReferencesProvider; exports.removeFindReferencesProvider = removeFindReferencesProvider; + exports.setMenuItemStateForLanguage = setMenuItemStateForLanguage; }); From 9f50db1f7f27d8fe672e81c73868b2e0fc385d84 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Mon, 22 Apr 2019 14:13:28 +0530 Subject: [PATCH 080/149] Fixed UriToPath Utill Function (#14706) * Fixed UriToPath Utill Function * fixed for new file and network file --- src/languageTools/PathConverters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languageTools/PathConverters.js b/src/languageTools/PathConverters.js index c4bbc7de898..8663a5478c2 100644 --- a/src/languageTools/PathConverters.js +++ b/src/languageTools/PathConverters.js @@ -36,7 +36,7 @@ define(function (require, exports, module) { let filePath = decodeURIComponent(url.pathname); if (brackets.platform === 'win') { - if (filePath[0] === '/') { + if (filePath && filePath.includes(":/") && filePath[0] === '/') { filePath = filePath.substr(1); } return filePath; From 3ff49544ef74d49d8dc387bda05c786c5e42ec9d Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Tue, 23 Apr 2019 05:47:15 -0700 Subject: [PATCH 081/149] Updated by ALF automation. --- src/nls/ja/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 09e0f3a5a16..14a9ef1c820 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -880,7 +880,7 @@ define({ ///String for Php Tooling Extensions "PHP_VERSION_INVALID": "PHP バージョンを解析する際のエラーです。“php –version” コマンドの出力を確認してください。", - "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールします。検出されたバージョン: {0}", + "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールしてください。", "PHP_EXECUTABLE_NOT_FOUND": "PHP ランタイムが見つかりません。PHP7 ランタイムをインストールして、PHP の環境設定で適切に “executablePath” を更新してください。これにより、コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールが有効になります。", "PHP_PROCESS_SPAWN_ERROR": "PHP プロセスを起動中に、エラーコード {0} が発生しました。", "PHP_SERVER_ERROR_TITLE": "エラー", From 396a3b8f15e5ac3df08d75ba5c3d32327798c940 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Tue, 23 Apr 2019 18:46:17 +0530 Subject: [PATCH 082/149] Fixed Blank Error Dialog Issue (#14719) --- src/extensions/default/PhpTooling/main.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js index 8377c62b72b..017b7cb0232 100755 --- a/src/extensions/default/PhpTooling/main.js +++ b/src/extensions/default/PhpTooling/main.js @@ -176,10 +176,18 @@ define(function (require, exports, module) { } function showErrorPopUp(err) { + if(!err) { + return; + } + var localizedErrStr = ""; if (typeof (err) === "string") { - err = Strings[err]; + localizedErrStr = Strings[err]; } else { - err = StringUtils.format(Strings[err[0]], err[1]); + localizedErrStr = StringUtils.format(Strings[err[0]], err[1]); + } + if(!localizedErrStr) { + console.error("Php Tooling Error: " + err); + return; } var Buttons = [ { className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, @@ -190,7 +198,7 @@ define(function (require, exports, module) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.PHP_SERVER_ERROR_TITLE, - err, + localizedErrStr, Buttons ).done(function (id) { if (id === Dialogs.DIALOG_BTN_DOWNLOAD) { From 85d28385bb3ef317062060d22b02493adc8a6a47 Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Tue, 23 Apr 2019 08:08:25 -0700 Subject: [PATCH 083/149] Updated by ALF automation. --- src/nls/ja/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 14a9ef1c820..575fdfbcd73 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -880,7 +880,7 @@ define({ ///String for Php Tooling Extensions "PHP_VERSION_INVALID": "PHP バージョンを解析する際のエラーです。“php –version” コマンドの出力を確認してください。", - "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールしてください。", + "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールしてください。検出されたバージョン: {0}", "PHP_EXECUTABLE_NOT_FOUND": "PHP ランタイムが見つかりません。PHP7 ランタイムをインストールして、PHP の環境設定で適切に “executablePath” を更新してください。これにより、コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールが有効になります。", "PHP_PROCESS_SPAWN_ERROR": "PHP プロセスを起動中に、エラーコード {0} が発生しました。", "PHP_SERVER_ERROR_TITLE": "エラー", From 6c927491d7895fd1a0adada76721ff5e2fab4c37 Mon Sep 17 00:00:00 2001 From: walfgithub Date: Tue, 23 Apr 2019 08:16:49 -0700 Subject: [PATCH 084/149] ALF Automation (#14702) * Updated by ALF automation. * Fixing the LANGUAGE_TOOLS_PREFERENCES key in strings.js * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. --- src/nls/fr/strings.js | 11 ++++++++++- src/nls/ja/strings.js | 13 +++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index f69da6a973b..50c650e88b7 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -419,6 +419,7 @@ define({ "CMD_QUICK_OPEN": "Ouverture rapide", "CMD_GOTO_LINE": "Atteindre la ligne", "CMD_GOTO_DEFINITION": "Accès rapide à la définition", + "CMD_GOTO_DEFINITION_PROJECT": "Accès rapide à la définition dans le projet", "CMD_GOTO_FIRST_PROBLEM": "Accéder au premier problème", "CMD_TOGGLE_QUICK_EDIT": "Édition rapide", "CMD_TOGGLE_QUICK_DOCS": "Documentation rapide", @@ -888,5 +889,13 @@ define({ "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "Préférences pour Language Tools", + + "FIND_ALL_REFERENCES": "Rechercher toutes les références", + "REFERENCES_IN_FILES": "références", + "REFERENCE_IN_FILES": "référence", + "REFERENCES_NO_RESULTS": "Références non disponibles pour la position actuelle du curseur", + + "CMD_FIND_DOCUMENT_SYMBOLS": "Rechercher des symboles de document", + "CMD_FIND_PROJECT_SYMBOLS": "Rechercher des symboles de projet" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 4e50b038526..575fdfbcd73 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -419,6 +419,7 @@ define({ "CMD_QUICK_OPEN": "クイックオープン", "CMD_GOTO_LINE": "行に移動", "CMD_GOTO_DEFINITION": "定義をクイック検索", + "CMD_GOTO_DEFINITION_PROJECT": "プロジェクトで定義をクイック検索", "CMD_GOTO_FIRST_PROBLEM": "最初の問題に移動", "CMD_TOGGLE_QUICK_EDIT": "クイック編集", "CMD_TOGGLE_QUICK_DOCS": "クイックドキュメント", @@ -879,7 +880,7 @@ define({ ///String for Php Tooling Extensions "PHP_VERSION_INVALID": "PHP バージョンを解析する際のエラーです。“php –version” コマンドの出力を確認してください。", - "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールします。検出されたバージョン: {0}", + "PHP_UNSUPPORTED_VERSION": "コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールを有効化するために、PHP7 ランタイムをインストールしてください。検出されたバージョン: {0}", "PHP_EXECUTABLE_NOT_FOUND": "PHP ランタイムが見つかりません。PHP7 ランタイムをインストールして、PHP の環境設定で適切に “executablePath” を更新してください。これにより、コードヒント、パラメーターヒント、定義にジャンプなどの PHP 関連のツールが有効になります。", "PHP_PROCESS_SPAWN_ERROR": "PHP プロセスを起動中に、エラーコード {0} が発生しました。", "PHP_SERVER_ERROR_TITLE": "エラー", @@ -888,5 +889,13 @@ define({ "OPEN_PREFERENNCES": "環境設定を開く", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Preferences for Language Tools" + "LANGUAGE_TOOLS_PREFERENCES": "言語ツールの設定", + + "FIND_ALL_REFERENCES": "すべての参照を検索", + "REFERENCES_IN_FILES": "参照", + "REFERENCE_IN_FILES": "参照", + "REFERENCES_NO_RESULTS": "現在のカーソル位置で利用可能な参照はありません", + + "CMD_FIND_DOCUMENT_SYMBOLS": "ドキュメント記号を検索", + "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索" }); From 5e78d0cc91c24face450042b69a811b2f58ae9fc Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Wed, 24 Apr 2019 03:08:48 -0700 Subject: [PATCH 085/149] Updated by ALF automation. --- src/nls/ja/strings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 575fdfbcd73..33eea03af54 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "{APP_NAME} の正常性レポート", "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する偽名情報を共有します", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    お客様のデータを確認するには、または、データを共有しないように選択するには、ヘルプ/正常性レポートを選択してください。

    {APP_NAME} の正常性レポートに関する詳細情報", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    ヘルプ/正常性レポートから、お客様のデータの確認またはデータの非共有の選択をすることができます。

    {APP_NAME} の正常性レポートに関する詳細情報", "HEALTH_DATA_PREVIEW": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについて、またこれが {APP_NAME} コミュニティにどのように役立ち、プライバシーを保護するかついて詳細をご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについての詳細およびレポートがお客様のプライバシーを保護した上でどのように {APP_NAME} コミュニティに役立つかをご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "時間", From b5e8b5d595ffc250aeeccc54375fd28157e14500 Mon Sep 17 00:00:00 2001 From: walfgithub Date: Wed, 24 Apr 2019 03:24:15 -0700 Subject: [PATCH 086/149] ALF Automation (#14721) * Updated by ALF automation. * Fixing the LANGUAGE_TOOLS_PREFERENCES key in strings.js * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. --- src/nls/ja/strings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 575fdfbcd73..33eea03af54 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "{APP_NAME} の正常性レポート", "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する偽名情報を共有します", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    お客様のデータを確認するには、または、データを共有しないように選択するには、ヘルプ/正常性レポートを選択してください。

    {APP_NAME} の正常性レポートに関する詳細情報", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    ヘルプ/正常性レポートから、お客様のデータの確認またはデータの非共有の選択をすることができます。

    {APP_NAME} の正常性レポートに関する詳細情報", "HEALTH_DATA_PREVIEW": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについて、またこれが {APP_NAME} コミュニティにどのように役立ち、プライバシーを保護するかついて詳細をご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについての詳細およびレポートがお客様のプライバシーを保護した上でどのように {APP_NAME} コミュニティに役立つかをご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "時間", From 16ed866fc50be27b76402d87e8b1f6e28a268ba7 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Wed, 24 Apr 2019 19:04:10 +0530 Subject: [PATCH 087/149] Changed Health Data Pop Up String to point to new wiki URL (#14709) --- src/nls/root/strings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 2668105fa05..dc74e2f354c 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION" : "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE" : "{APP_NAME} Health Report", "HEALTH_DATA_DO_TRACK" : "Share pseudonymous information on how I use {APP_NAME}", - "HEALTH_DATA_NOTIFICATION_MESSAGE" : "In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

    You can see your data or choose not to share data by selecting Help > Health Report.

    Learn more about {APP_NAME} Health Report", + "HEALTH_DATA_NOTIFICATION_MESSAGE" : "In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

    You can see your data or choose not to share data by selecting Help > Health Report.

    Learn more about {APP_NAME} Health Report", "HEALTH_DATA_PREVIEW" : "{APP_NAME} Health Report", - "HEALTH_DATA_PREVIEW_INTRO" : "

    In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

    Below is a preview of the data that will be sent in your next Health Report if it is enabled.

    ", + "HEALTH_DATA_PREVIEW_INTRO" : "

    In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

    Below is a preview of the data that will be sent in your next Health Report if it is enabled.

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME" : "Time", From 0f81ee17ac88ee93c55180519fa05d21e50afccb Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Wed, 24 Apr 2019 09:55:44 -0700 Subject: [PATCH 088/149] Updated by ALF automation. --- src/nls/fr/strings.js | 4 ++-- src/nls/ja/strings.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 50c650e88b7..be884a59716 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "Rapport d’intégrité de {APP_NAME}", "HEALTH_DATA_DO_TRACK": "Partager des informations pseudonymes sur la façon dont j’utilise {APP_NAME}", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

    Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

    Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

    You can see your data or choose not to share data by selecting Help > Health Report.

    Learn more about {APP_NAME} Health Report", "HEALTH_DATA_PREVIEW": "Rapport d’intégrité de {APP_NAME}", - "HEALTH_DATA_PREVIEW_INTRO": "

    Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

    Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

    Below is a preview of the data that will be sent in your next Health Report if it is enabled.

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "Temps", diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 33eea03af54..fa88b7b2acc 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "{APP_NAME} の正常性レポート", "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する偽名情報を共有します", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    ヘルプ/正常性レポートから、お客様のデータの確認またはデータの非共有の選択をすることができます。

    {APP_NAME} の正常性レポートに関する詳細情報", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

    You can see your data or choose not to share data by selecting Help > Health Report.

    Learn more about {APP_NAME} Health Report", "HEALTH_DATA_PREVIEW": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについての詳細およびレポートがお客様のプライバシーを保護した上でどのように {APP_NAME} コミュニティに役立つかをご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

    Below is a preview of the data that will be sent in your next Health Report if it is enabled.

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "時間", From 6db26a250c31d8d24b8ddbe1c2cf21c0e73392b4 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Thu, 25 Apr 2019 09:49:11 +0530 Subject: [PATCH 089/149] Reinitiate tooling service on Node process crash (#14724) * Fix for node process failing * Modification #1 * Code cleanup * Code cleanup #1 * Code cleanup #2: Removing timeout and adding comments * Fixing Logging issue --- .../default/PhpTooling/CodeHintsProvider.js | 4 + .../default/PhpTooling/PHPSymbolProviders.js | 12 ++- src/extensions/default/PhpTooling/main.js | 56 +++++++++++--- src/languageTools/ClientLoader.js | 77 +++++++++++++++---- src/languageTools/DefaultProviders.js | 18 ++++- .../node/RegisterLanguageClientInfo.js | 10 ++- 6 files changed, 151 insertions(+), 26 deletions(-) diff --git a/src/extensions/default/PhpTooling/CodeHintsProvider.js b/src/extensions/default/PhpTooling/CodeHintsProvider.js index 3f54d82f2e1..958dcd33238 100644 --- a/src/extensions/default/PhpTooling/CodeHintsProvider.js +++ b/src/extensions/default/PhpTooling/CodeHintsProvider.js @@ -54,6 +54,10 @@ define(function (require, exports, module) { this.defaultCodeHintProviders = new DefaultProviders.CodeHintsProvider(client); } + CodeHintsProvider.prototype.setClient = function (client) { + this.defaultCodeHintProviders.setClient(client); + }; + function setStyleAndCacheToken($hintObj, token) { $hintObj.addClass('brackets-hints-with-type-details'); $hintObj.data('completionItem', token); diff --git a/src/extensions/default/PhpTooling/PHPSymbolProviders.js b/src/extensions/default/PhpTooling/PHPSymbolProviders.js index 8517e1db574..9265bd16dda 100644 --- a/src/extensions/default/PhpTooling/PHPSymbolProviders.js +++ b/src/extensions/default/PhpTooling/PHPSymbolProviders.js @@ -22,7 +22,7 @@ */ /*jslint regexp: true */ - +/*eslint no-invalid-this: 0, max-len: 0*/ define(function (require, exports, module) { "use strict"; @@ -34,6 +34,12 @@ define(function (require, exports, module) { var SymbolKind = QuickOpen.SymbolKind; + function setClient(client) { + if (client) { + this.client = client; + } + } + function convertRangePosToEditorPos(rangePos) { return { line: rangePos.line, @@ -112,6 +118,8 @@ define(function (require, exports, module) { this.client = client; } + DocumentSymbolsProvider.prototype.setClient = setClient; + DocumentSymbolsProvider.prototype.match = function (query) { return query.startsWith("@"); }; @@ -171,6 +179,8 @@ define(function (require, exports, module) { this.client = client; } + ProjectSymbolsProvider.prototype.setClient = setClient; + ProjectSymbolsProvider.prototype.match = function (query) { return query.startsWith("#"); }; diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js index 017b7cb0232..fad20cd2429 100755 --- a/src/extensions/default/PhpTooling/main.js +++ b/src/extensions/default/PhpTooling/main.js @@ -24,6 +24,7 @@ define(function (require, exports, module) { "use strict"; var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + ClientLoader = brackets.getModule("languageTools/ClientLoader"), AppInit = brackets.getModule("utils/AppInit"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), ProjectManager = brackets.getModule("project/ProjectManager"), @@ -61,13 +62,14 @@ define(function (require, exports, module) { phpServerRunning = false, serverCapabilities, currentRootPath, - chProvider, - phProvider, - lProvider, - jdProvider, - dSymProvider, - pSymProvider, - refProvider; + chProvider = null, + phProvider = null, + lProvider = null, + jdProvider = null, + dSymProvider = null, + pSymProvider = null, + refProvider = null, + providersRegistered = false; PreferencesManager.definePreference("php", "object", phpConfig, { description: Strings.DESCRIPTION_PHP_TOOLING_CONFIGURATION @@ -103,6 +105,18 @@ define(function (require, exports, module) { } }; + function resetClientInProviders() { + var logErr = "PhpTooling: Can't reset client for : "; + chProvider ? chProvider.setClient(_client) : console.log(logErr, "CodeHintsProvider"); + phProvider ? phProvider.setClient(_client) : console.log(logErr, "ParameterHintsProvider"); + jdProvider ? jdProvider.setClient(_client) : console.log(logErr, "JumpToDefProvider"); + dSymProvider ? dSymProvider.setClient(_client) : console.log(logErr, "DocumentSymbolsProvider"); + pSymProvider ? pSymProvider.setClient(_client) : console.log(logErr, "ProjectSymbolsProvider"); + refProvider ? refProvider.setClient(_client) : console.log(logErr, "FindReferencesProvider"); + lProvider ? lProvider.setClient(_client) : console.log(logErr, "LintingProvider"); + _client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider)); + } + function registerToolingProviders() { chProvider = new CodeHintsProvider(_client), phProvider = new DefaultProviders.ParameterHintsProvider(_client), @@ -147,6 +161,8 @@ define(function (require, exports, module) { CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(true); _client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider)); + + providersRegistered = true; } function addEventHandlers() { @@ -214,7 +230,13 @@ define(function (require, exports, module) { function handlePostPhpServerStart() { if (!phpServerRunning) { phpServerRunning = true; - registerToolingProviders(); + + if (providersRegistered) { + resetClientInProviders(); + } else { + registerToolingProviders(); + } + addEventHandlers(); EditorManager.off("activeEditorChange.php"); LanguageManager.off("languageModified.php"); @@ -262,13 +284,29 @@ define(function (require, exports, module) { } } - AppInit.appReady(function () { + function initiateService(evt, onAppReady) { + if (onAppReady) { + console.log("Php tooling: Starting the service"); + } else { + console.log("Php tooling: Something went wrong. Restarting the service"); + } + + phpServerRunning = false; LanguageTools.initiateToolingService(clientName, clientFilePath, ['php']).done(function (client) { _client = client; + //Attach only once + EditorManager.off("activeEditorChange.php"); EditorManager.on("activeEditorChange.php", activeEditorChangeHandler); + //Attach only once + LanguageManager.off("languageModified.php"); LanguageManager.on("languageModified.php", languageModifiedHandler); activeEditorChangeHandler(null, EditorManager.getActiveEditor()); }); + } + + AppInit.appReady(function () { + initiateService(null, true); + ClientLoader.on("languageClientModuleInitialized", initiateService); }); //Only for Unit testing diff --git a/src/languageTools/ClientLoader.js b/src/languageTools/ClientLoader.js index c2fa615da6b..63d5c28784a 100644 --- a/src/languageTools/ClientLoader.js +++ b/src/languageTools/ClientLoader.js @@ -29,8 +29,10 @@ define(function (require, exports, module) { var ToolingInfo = JSON.parse(require("text!languageTools/ToolingInfo.json")), NodeDomain = require("utils/NodeDomain"), FileUtils = require("file/FileUtils"), + EventDispatcher = require("utils/EventDispatcher"), BracketsToNodeInterface = require("languageTools/BracketsToNodeInterface").BracketsToNodeInterface; + EventDispatcher.makeEventDispatcher(exports); //Register paths required for Language Client and also register default brackets capabilities. var _bracketsPath = FileUtils.getNativeBracketsDirectoryPath(); // The native directory path ends with either "test" or "src". @@ -39,22 +41,12 @@ define(function (require, exports, module) { var _modulePath = FileUtils.getNativeModuleDirectoryPath(module), _nodePath = "node/RegisterLanguageClientInfo", _domainPath = [_bracketsPath, _modulePath, _nodePath].join("/"), - clientInfoDomain = new NodeDomain("LanguageClientInfo", _domainPath), - //Init node with Information required by Language Client - clientInfoLoadedPromise = clientInfoDomain.exec("initialize", _bracketsPath, ToolingInfo), + clientInfoDomain = null, + clientInfoLoadedPromise = null, //Clients that have to be loaded once the LanguageClient info is successfully loaded on the //node side. pendingClientsToBeLoaded = []; - //Attach success and failure function for the clientInfoLoadedPromise - clientInfoLoadedPromise.then(function () { - pendingClientsToBeLoaded.forEach(function (pendingClient) { - pendingClient.load(); - }); - }, function () { - console.log("Failed to Initialize LanguageClient Module Information."); - }); - function syncPrefsWithDomain(languageToolsPrefs) { if (clientInfoDomain) { clientInfoDomain.exec("syncPreferences", languageToolsPrefs); @@ -111,7 +103,7 @@ define(function (require, exports, module) { var result = $.Deferred(); //Only load clients after the LanguageClient Info has been initialized - if (clientInfoLoadedPromise.state() === "pending") { + if (!clientInfoLoadedPromise || clientInfoLoadedPromise.state() === "pending") { var pendingClient = { load: _clientLoader.bind(null, clientName, clientFilePath, result) }; @@ -123,6 +115,65 @@ define(function (require, exports, module) { return result; } + /** + * This function passes Brackets's native directory path as well as the tooling commands + * required by the LanguageClient node module. This information is then maintained in memory + * in the node process server for succesfully loading and functioning of all language clients + * since it is a direct dependency. + */ + function sendLanguageClientInfo() { + //Init node with Information required by Language Client + clientInfoLoadedPromise = clientInfoDomain.exec("initialize", _bracketsPath, ToolingInfo); + + function logInitializationError() { + console.error("Failed to Initialize LanguageClient Module Information."); + } + + //Attach success and failure function for the clientInfoLoadedPromise + clientInfoLoadedPromise.then(function (success) { + if (!success) { + logInitializationError(); + return; + } + + if (Array.isArray(pendingClientsToBeLoaded)) { + pendingClientsToBeLoaded.forEach(function (pendingClient) { + pendingClient.load(); + }); + } else { + exports.trigger("languageClientModuleInitialized"); + } + pendingClientsToBeLoaded = null; + }, function () { + logInitializationError(); + }); + } + + /** + * This function starts a domain which initializes the LanguageClient node module + * required by the Language Server Protocol framework in Brackets. All the LSP clients + * can only be successfully initiated once this domain has been successfully loaded and + * the LanguageClient info initialized. Refer to sendLanguageClientInfo for more. + */ + function initDomainAndHandleNodeCrash() { + clientInfoDomain = new NodeDomain("LanguageClientInfo", _domainPath); + //Initialize LanguageClientInfo once the domain has successfully loaded. + clientInfoDomain.promise().done(function () { + sendLanguageClientInfo(); + //This is to handle the node failure. If the node process dies, we get an on close + //event on the websocket connection object. Brackets then spawns another process and + //restablishes the connection. Once the connection is restablished we send reinitialize + //the LanguageClient info. + clientInfoDomain.connection.on("close", function (event, reconnectedPromise) { + reconnectedPromise.done(sendLanguageClientInfo); + }); + }).fail(function (err) { + console.error("ClientInfo domain could not be loaded: ", err); + }); + } + initDomainAndHandleNodeCrash(); + + exports.initiateLanguageClient = initiateLanguageClient; exports.syncPrefsWithDomain = syncPrefsWithDomain; }); diff --git a/src/languageTools/DefaultProviders.js b/src/languageTools/DefaultProviders.js index 47761ac0aa9..a75ea695de7 100644 --- a/src/languageTools/DefaultProviders.js +++ b/src/languageTools/DefaultProviders.js @@ -23,7 +23,7 @@ /*global Map*/ /* eslint-disable indent */ -/* eslint max-len: ["error", { "code": 200 }]*/ +/* eslint max-len: ["error", { "code": 200 }], no-invalid-this: 0*/ define(function (require, exports, module) { "use strict"; @@ -44,12 +44,20 @@ define(function (require, exports, module) { ExtensionUtils.loadStyleSheet(module, "styles/default_provider_style.css"); + function setClient(client) { + if (client) { + this.client = client; + } + } + function CodeHintsProvider(client) { this.client = client; this.query = ""; this.ignoreQuery = ["-", "->", ">", ":", "::", "(", "()", ")", "[", "[]", "]", "{", "{}", "}"]; } + CodeHintsProvider.prototype.setClient = setClient; + function formatTypeDataForToken($hintObj, token) { $hintObj.addClass('brackets-hints-with-type-details'); if (token.detail) { @@ -197,6 +205,8 @@ define(function (require, exports, module) { this.client = client; } + ParameterHintsProvider.prototype.setClient = setClient; + ParameterHintsProvider.prototype.hasParameterHints = function (editor, implicitChar) { if (!this.client) { return false; @@ -273,6 +283,8 @@ define(function (require, exports, module) { this.client = client; } + JumpToDefProvider.prototype.setClient = setClient; + JumpToDefProvider.prototype.canJumpToDef = function (editor, implicitChar) { if (!this.client) { return false; @@ -342,6 +354,8 @@ define(function (require, exports, module) { this._validateOnType = false; } + LintingProvider.prototype.setClient = setClient; + LintingProvider.prototype.clearExistingResults = function (filePath) { var filePathProvided = !!filePath; @@ -451,6 +465,8 @@ define(function (require, exports, module) { this.client = client; } + ReferencesProvider.prototype.setClient = setClient; + ReferencesProvider.prototype.hasReferences = function() { if (!this.client) { return false; diff --git a/src/languageTools/node/RegisterLanguageClientInfo.js b/src/languageTools/node/RegisterLanguageClientInfo.js index 614be647561..01b65fb7f1b 100644 --- a/src/languageTools/node/RegisterLanguageClientInfo.js +++ b/src/languageTools/node/RegisterLanguageClientInfo.js @@ -229,7 +229,11 @@ function syncPreferences(prefs) { global.LanguageClientInfo.preferences = prefs || global.LanguageClientInfo.preferences || {}; } -function initialize(bracketsSourcePath, toolingInfo) { +function initialize(bracketsSourcePath, toolingInfo, resolve) { + if (!bracketsSourcePath || !toolingInfo) { + resolve(true, null); //resolve with err param + } + var normalizedBracketsSourcePath = bracketsSourcePath.split(BACKWARD_SLASH).join(FORWARD_SLASH), bracketsSourcePathArray = normalizedBracketsSourcePath.split(FORWARD_SLASH), languageClientAbsolutePath = bracketsSourcePathArray.concat(LANGUAGE_CLIENT_RELATIVE_PATH_ARRAY).join(FORWARD_SLASH); @@ -239,6 +243,8 @@ function initialize(bracketsSourcePath, toolingInfo) { global.LanguageClientInfo.defaultBracketsCapabilities = defaultBracketsCapabilities; global.LanguageClientInfo.toolingInfo = toolingInfo; global.LanguageClientInfo.preferences = {}; + + resolve(null, true); //resolve with boolean denoting success } function init(domainManager) { @@ -253,7 +259,7 @@ function init(domainManager) { domainName, "initialize", initialize, - false, + true, "Initialize node environment for Language Client Module", [ { From 94d4433c3576278ea6fe5024c3f0b05ab88fd56d Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Wed, 24 Apr 2019 22:18:38 -0700 Subject: [PATCH 090/149] Updated by ALF automation. --- src/nls/fr/strings.js | 4 ++-- src/nls/ja/strings.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index be884a59716..84feba5606c 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "Rapport d’intégrité de {APP_NAME}", "HEALTH_DATA_DO_TRACK": "Partager des informations pseudonymes sur la façon dont j’utilise {APP_NAME}", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

    You can see your data or choose not to share data by selecting Help > Health Report.

    Learn more about {APP_NAME} Health Report", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

    Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

    Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", "HEALTH_DATA_PREVIEW": "Rapport d’intégrité de {APP_NAME}", - "HEALTH_DATA_PREVIEW_INTRO": "

    In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

    Below is a preview of the data that will be sent in your next Health Report if it is enabled.

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

    Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "Temps", diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index fa88b7b2acc..5a2a88733c7 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "{APP_NAME} の正常性レポート", "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する偽名情報を共有します", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues.

    You can see your data or choose not to share data by selecting Help > Health Report.

    Learn more about {APP_NAME} Health Report", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    ヘルプ/正常性レポートから、お客様のデータの確認またはデータの非共有の選択をすることができます。

    {APP_NAME} の正常性レポートに関する詳細情報", "HEALTH_DATA_PREVIEW": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_PREVIEW_INTRO": "

    In order to improve {APP_NAME}, we periodically send limited, pseudonymous statistics to Adobe about how you use {APP_NAME}. This information helps prioritize features, find bugs, and spot usability issues. Learn more about {APP_NAME} Health Report and how it benefits the {APP_NAME} community while protecting your privacy.

    Below is a preview of the data that will be sent in your next Health Report if it is enabled.

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについての詳細およびレポートがお客様のプライバシーを保護した上でどのように {APP_NAME} コミュニティに役立つかをご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "時間", From 6b6b597b7bd0fba0ae59c0409dc151c0443127e6 Mon Sep 17 00:00:00 2001 From: walfgithub Date: Wed, 24 Apr 2019 22:57:38 -0700 Subject: [PATCH 091/149] ALF Automation (#14723) * Updated by ALF automation. * Fixing the LANGUAGE_TOOLS_PREFERENCES key in strings.js * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. --- src/nls/fr/strings.js | 4 ++-- src/nls/ja/strings.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 50c650e88b7..84feba5606c 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "Rapport d’intégrité de {APP_NAME}", "HEALTH_DATA_DO_TRACK": "Partager des informations pseudonymes sur la façon dont j’utilise {APP_NAME}", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

    Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

    Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation.

    Pour voir les renseignements collectés et choisir ceux que vous ne souhaitez pas partager, cliquez sur Aide > Rapport d’intégrité.

    Lisez cet article pour en savoir plus concernant le rapport d’intégrité de {APP_NAME}", "HEALTH_DATA_PREVIEW": "Rapport d’intégrité de {APP_NAME}", - "HEALTH_DATA_PREVIEW_INTRO": "

    Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

    Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    Afin d’améliorer {APP_NAME}, nous transmettons régulièrement des statistiques limitées et pseudonymes à Adobe sur la manière dont vous utilisez {APP_NAME}. Ces données permettent de hiérarchiser les fonctionnalités à traiter, de détecter les bugs éventuels et d’identifier les problèmes d’utilisation. Lisez cet article concernant le rapport d’intégrité de {APP_NAME} et découvrez en quoi il est utile à la communauté {APP_NAME} tout en préservant votre confidentialité.

    Vous trouverez ci-dessous un résumé des données qui seront envoyées dans le cadre de votre prochain rapport d’intégrité si vous décidez d’activer cette option.

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "Temps", diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 33eea03af54..5a2a88733c7 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -652,9 +652,9 @@ define({ "HEALTH_DATA_NOTIFICATION": "Health Report Preferences", "HEALTH_FIRST_POPUP_TITLE": "{APP_NAME} の正常性レポート", "HEALTH_DATA_DO_TRACK": "{APP_NAME} の使用方法に関する偽名情報を共有します", - "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    ヘルプ/正常性レポートから、お客様のデータの確認またはデータの非共有の選択をすることができます。

    {APP_NAME} の正常性レポートに関する詳細情報", + "HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。

    ヘルプ/正常性レポートから、お客様のデータの確認またはデータの非共有の選択をすることができます。

    {APP_NAME} の正常性レポートに関する詳細情報", "HEALTH_DATA_PREVIEW": "{APP_NAME} の正常性レポート", - "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについての詳細およびレポートがお客様のプライバシーを保護した上でどのように {APP_NAME} コミュニティに役立つかをご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", + "HEALTH_DATA_PREVIEW_INTRO": "

    {APP_NAME} 品質向上のため、アドビでは、お客様の {APP_NAME} の使用方法に関する限られた偽名の統計をアドビに定期的に送信しています。この情報は、機能を優先順位付けし、バグを発見し、操作性の問題を検出する際に役立ちます。{APP_NAME} の正常性レポートについての詳細およびレポートがお客様のプライバシーを保護した上でどのように {APP_NAME} コミュニティに役立つかをご確認ください。

    有効にした場合に、次回のお客様の正常性レポートで送信されるデータのプレビューを以下に示します。

    ", // extensions/default/InlineTimingFunctionEditor "INLINE_TIMING_EDITOR_TIME": "時間", From 2e0702e240bd10ae60c462d7712f404f6f4d5565 Mon Sep 17 00:00:00 2001 From: Localization Utility Date: Thu, 25 Apr 2019 01:43:28 -0700 Subject: [PATCH 092/149] Updated by ALF automation. --- src/nls/fr/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 84feba5606c..866b74cba2f 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -889,7 +889,7 @@ define({ "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Préférences pour Language Tools", + "LANGUAGE_TOOLS_PREFERENCES": "Préférences pour les outils linguistiques", "FIND_ALL_REFERENCES": "Rechercher toutes les références", "REFERENCES_IN_FILES": "références", From 1b13b49a6b0a612faf36c95985e07b423cc78dae Mon Sep 17 00:00:00 2001 From: walfgithub Date: Thu, 25 Apr 2019 06:43:05 -0700 Subject: [PATCH 093/149] ALF Automation (#14727) * Updated by ALF automation. * Fixing the LANGUAGE_TOOLS_PREFERENCES key in strings.js * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. * Updated by ALF automation. --- src/nls/fr/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 84feba5606c..866b74cba2f 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -889,7 +889,7 @@ define({ "OPEN_PREFERENNCES": "Ouvrir les préférences", //Strings for LanguageTools Preferences - "LANGUAGE_TOOLS_PREFERENCES": "Préférences pour Language Tools", + "LANGUAGE_TOOLS_PREFERENCES": "Préférences pour les outils linguistiques", "FIND_ALL_REFERENCES": "Rechercher toutes les références", "REFERENCES_IN_FILES": "références", From 188c125abfb5cb3fd17fa1a1367bd6e0fc921105 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Thu, 25 Apr 2019 22:33:07 +0530 Subject: [PATCH 094/149] Fix for CodeHint Description CSS Issue (#14728) * Fix for overlapping dropdown div * Cleanup #1 --- src/editor/CodeHintList.js | 6 +----- src/styles/brackets_patterns_override.less | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/editor/CodeHintList.js b/src/editor/CodeHintList.js index 5a132281d75..23380301d2d 100644 --- a/src/editor/CodeHintList.js +++ b/src/editor/CodeHintList.js @@ -266,16 +266,12 @@ define(function (require, exports, module) { // attach to DOM $parent.append($ul); - $parent.find(".hint-list-offset").remove(); - $("
    ").appendTo($parent); - // If a a description field requested attach one if (this.enableDescription) { // Remove the desc element first to ensure DOM order $parent.find("#codehint-desc").remove(); $parent.append(""); $ul.addClass("withDesc"); - $parent.find(".hint-list-offset").addClass("withDesc"); } this._setSelectedIndex(selectInitial ? 0 : -1); } @@ -323,7 +319,7 @@ define(function (require, exports, module) { if (descOffset === 0) { descOffset = menuHeight - descOverhang; } - this.$hintMenu.find(".hint-list-offset").css("height", descOffset - 1); + this.$hintMenu.find("#codehint-desc").css("margin-top", descOffset - 1); return {left: posLeft, top: posTop, width: availableWidth}; }; diff --git a/src/styles/brackets_patterns_override.less b/src/styles/brackets_patterns_override.less index e1de2ba3902..6ce6c1aee15 100644 --- a/src/styles/brackets_patterns_override.less +++ b/src/styles/brackets_patterns_override.less @@ -586,11 +586,10 @@ a:focus { #codehint-desc { background: @bc-codehint-desc; - position: relative; + position: absolute; width: 100%; box-sizing: border-box; float: left; - top: 34px; border-radius: 1px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; From 83eeae5df31bf1a1b705734bfb339082dfe5aa49 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Fri, 26 Apr 2019 15:20:42 +0530 Subject: [PATCH 095/149] Fixed Code/Parameter Hints Issue for large file (#14722) * Fixed Code/Parameter Hints Issue for large file * Addressed review comments * Addressed review comments --- src/extensions/default/PhpTooling/CodeHintsProvider.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/PhpTooling/CodeHintsProvider.js b/src/extensions/default/PhpTooling/CodeHintsProvider.js index 958dcd33238..1029c9ccacf 100644 --- a/src/extensions/default/PhpTooling/CodeHintsProvider.js +++ b/src/extensions/default/PhpTooling/CodeHintsProvider.js @@ -91,9 +91,15 @@ define(function (require, exports, module) { pos = editor.getCursorPos(), docPath = editor.document.file._path, $deferredHints = $.Deferred(), - self = this.defaultCodeHintProviders; + self = this.defaultCodeHintProviders, + client = this.defaultCodeHintProviders.client; - this.defaultCodeHintProviders.client.requestHints({ + //Make sure the document is in sync with the server + client.notifyTextDocumentChanged({ + filePath: docPath, + fileContent: editor.document.getText() + }); + client.requestHints({ filePath: docPath, cursorPos: pos }).done(function (msgObj) { From ee01b79490cbb8a04d6454c5ad0f8beab654808f Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Fri, 26 Apr 2019 15:31:09 +0530 Subject: [PATCH 096/149] InApp Notifications (#14715) * InApp Notification * Minor cleanup * Remove template parameters * MIn height for notification bar * Address code review comments --- src/brackets.config.dev.json | 1 + src/brackets.config.dist.json | 1 + src/brackets.config.prerelease.json | 1 + .../htmlContent/notificationContainer.html | 7 + .../default/InAppNotifications/main.js | 324 ++++++++++++++++++ .../InAppNotifications/styles/styles.css | 56 +++ 6 files changed, 390 insertions(+) create mode 100644 src/extensions/default/InAppNotifications/htmlContent/notificationContainer.html create mode 100644 src/extensions/default/InAppNotifications/main.js create mode 100644 src/extensions/default/InAppNotifications/styles/styles.css diff --git a/src/brackets.config.dev.json b/src/brackets.config.dev.json index bebcd37a9a2..76137056939 100644 --- a/src/brackets.config.dev.json +++ b/src/brackets.config.dev.json @@ -4,5 +4,6 @@ "serviceKey" : "brackets-service", "environment" : "stage", "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json", + "notification_info_url" : "https://s3.amazonaws.com/files.brackets.io/notifications/prerelease/.json", "buildtype" : "dev" } diff --git a/src/brackets.config.dist.json b/src/brackets.config.dist.json index 9fdc775054b..c3411b31a28 100644 --- a/src/brackets.config.dist.json +++ b/src/brackets.config.dist.json @@ -4,5 +4,6 @@ "serviceKey" : "brackets-service", "environment" : "production", "update_info_url" : "https://getupdates.brackets.io/getupdates/", + "notification_info_url" : "https://getupdates.brackets.io/getnotifications?locale=", "buildtype" : "production" } diff --git a/src/brackets.config.prerelease.json b/src/brackets.config.prerelease.json index ee527136d4b..ac8e9de01e9 100644 --- a/src/brackets.config.prerelease.json +++ b/src/brackets.config.prerelease.json @@ -4,5 +4,6 @@ "serviceKey" : "brackets-service", "environment" : "production", "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json", + "notification_info_url" : "https://s3.amazonaws.com/files.brackets.io/notifications/prerelease/.json", "buildtype" : "prerelease" } diff --git a/src/extensions/default/InAppNotifications/htmlContent/notificationContainer.html b/src/extensions/default/InAppNotifications/htmlContent/notificationContainer.html new file mode 100644 index 00000000000..b420dee0e70 --- /dev/null +++ b/src/extensions/default/InAppNotifications/htmlContent/notificationContainer.html @@ -0,0 +1,7 @@ +
    +
    +
    +
    + +
    +
    diff --git a/src/extensions/default/InAppNotifications/main.js b/src/extensions/default/InAppNotifications/main.js new file mode 100644 index 00000000000..f51684006cb --- /dev/null +++ b/src/extensions/default/InAppNotifications/main.js @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2019 - present Adobe. 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. + * + */ + +/** + * module for displaying in-app notifications + * + */ +define(function (require, exports, module) { + "use strict"; + + var AppInit = brackets.getModule("utils/AppInit"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + ExtensionManager = brackets.getModule("extensibility/ExtensionManager"), + HealthLogger = brackets.getModule("utils/HealthLogger"), + NotificationBarHtml = require("text!htmlContent/notificationContainer.html"); + + ExtensionUtils.loadStyleSheet(module, "styles/styles.css"); + + // duration of one day in milliseconds + var ONE_DAY = 1000 * 60 * 60 * 24; + + // Init default last notification number + PreferencesManager.stateManager.definePreference("lastHandledNotificationNumber", "number", 0); + + // Init default last info URL fetch time + PreferencesManager.stateManager.definePreference("lastNotificationURLFetchTime", "number", 0); + + /** + * Constructs notification info URL for XHR + * + * @param {string=} localeParam - optional locale, defaults to 'brackets.getLocale()' when omitted. + * @returns {string} the new notification info url + */ + function _getVersionInfoUrl(localeParam) { + + var locale = localeParam || brackets.getLocale(); + + if (locale.length > 2) { + locale = locale.substring(0, 2); + } + + return brackets.config.notification_info_url.replace("", locale); + } + + /** + * Get a data structure that has information for all Brackets targeted notifications. + * + * _notificationInfoUrl is used for unit testing. + */ + function _getNotificationInformation(_notificationInfoUrl) { + // Last time the versionInfoURL was fetched + var lastInfoURLFetchTime = PreferencesManager.getViewState("lastNotificationURLFetchTime"); + + var result = new $.Deferred(); + var fetchData = false; + var data; + + // If we don't have data saved in prefs, fetch + data = PreferencesManager.getViewState("notificationInfo"); + if (!data) { + fetchData = true; + } + + // If more than 24 hours have passed since our last fetch, fetch again + if (Date.now() > lastInfoURLFetchTime + ONE_DAY) { + fetchData = true; + } + + if (fetchData) { + var lookupPromise = new $.Deferred(), + localNotificationInfoUrl; + + // If the current locale isn't "en" or "en-US", check whether we actually have a + // locale-specific notification target, and fall back to "en" if not. + var locale = brackets.getLocale().toLowerCase(); + if (locale !== "en" && locale !== "en-us") { + localNotificationInfoUrl = _notificationInfoUrl || _getVersionInfoUrl(); + // Check if we can reach a locale specific notifications source + $.ajax({ + url: localNotificationInfoUrl, + cache: false, + type: "HEAD" + }).fail(function (jqXHR, status, error) { + // Fallback to "en" locale + localNotificationInfoUrl = _getVersionInfoUrl("en"); + }).always(function (jqXHR, status, error) { + lookupPromise.resolve(); + }); + } else { + localNotificationInfoUrl = _notificationInfoUrl || _getVersionInfoUrl("en"); + lookupPromise.resolve(); + } + + lookupPromise.done(function () { + $.ajax({ + url: localNotificationInfoUrl, + dataType: "json", + cache: false + }).done(function (notificationInfo, textStatus, jqXHR) { + lastInfoURLFetchTime = (new Date()).getTime(); + PreferencesManager.setViewState("lastNotificationURLFetchTime", lastInfoURLFetchTime); + PreferencesManager.setViewState("notificationInfo", notificationInfo); + result.resolve(notificationInfo); + }).fail(function (jqXHR, status, error) { + // When loading data for unit tests, the error handler is + // called but the responseText is valid. Try to use it here, + // but *don't* save the results in prefs. + + if (!jqXHR.responseText) { + // Text is NULL or empty string, reject(). + result.reject(); + return; + } + + try { + data = JSON.parse(jqXHR.responseText); + result.resolve(data); + } catch (e) { + result.reject(); + } + }); + }); + } else { + result.resolve(data); + } + + return result.promise(); + } + + + /** + * Check for notifications, notification overlays are always displayed + * + * @return {$.Promise} jQuery Promise object that is resolved or rejected after the notification check is complete. + */ + function checkForNotification(versionInfoUrl) { + var result = new $.Deferred(); + + _getNotificationInformation(versionInfoUrl) + .done(function (notificationInfo) { + // Get all available notifications + var notifications = notificationInfo.notifications; + if (notifications && notifications.length > 0) { + // Iterate through notifications and act only on the most recent + // applicable notification + notifications.every(function(notificationObj) { + // Only show the notification overlay if the user hasn't been + // alerted of this notification + if (_checkNotificationValidity(notificationObj)) { + if (notificationObj.silent) { + // silent notifications, to gather user validity based on filters + HealthLogger.sendAnalyticsData("notification", notificationObj.sequence, "handled"); + } else { + showNotification(notificationObj); + } + // Break, we have acted on one notification already + return false; + } + // Continue, we haven't yet got a notification to act on + return true; + }); + } + result.resolve(); + }) + .fail(function () { + // Error fetching the update data. If this is a forced check, alert the user + result.reject(); + }); + + return result.promise(); + } + + function _checkPlatform(filters, _platform) { + return !filters.platforms || filters.platforms.length === 0 || filters.platforms.indexOf(_platform) >=0; + } + + function _checkBuild(filters, _build) { + return !filters.builds || filters.builds.length === 0 || filters.builds.indexOf(_build) >=0; + } + + function _checkVersion(filters, _version) { + var re = new RegExp(filters.version); + return re.exec(_version); + } + + function _checkLocale(filters, _locale) { + return !filters.locales || filters.locales.length === 0 || filters.locales.indexOf(_locale) >=0; + } + + function _checkExpiry(expiry) { + return Date.now() <= expiry; + } + + function _checkExtensions(filters) { + var allExtensions = ExtensionManager.extensions, + allExtnsMatched = true, + userExtensionKeys = Object.keys(allExtensions).filter(function(k) { + return allExtensions[k].installInfo.locationType === 'user'; + }); + + if (!filters.extensions) { + allExtnsMatched = userExtensionKeys.size === 0; + } else if (filters.extensions.length === 0) { + allExtnsMatched = userExtensionKeys.length > 0; + } else { + var filteredExtns = filters.extensions, + extnIterator = null; + for (var i=0; i < filteredExtns.length; i++) { + extnIterator = filteredExtns[i]; + if (userExtensionKeys.indexOf(extnIterator) === -1) { + allExtnsMatched = false; + break; + } + } + } + return allExtnsMatched; + } + + function _checkNotificationValidity(notificationObj) { + + var filters = notificationObj.filters, + _platform = brackets.getPlatformInfo(), + _locale = brackets.getLocale(), + _lastHandledNotificationNumber = PreferencesManager.getViewState("lastHandledNotificationNumber"), + // Extract current build number from package.json version field 0.0.0-0 + _buildNumber = Number(/-([0-9]+)/.exec(brackets.metadata.version)[1]), + _version = brackets.metadata.apiVersion; + + if(_locale.length > 2) { + _locale = _locale.substring(0, 2); + } + + return notificationObj.sequence > _lastHandledNotificationNumber + && _checkExpiry(notificationObj.expiry) + && _checkPlatform(filters, _platform) + && _checkLocale(filters, _locale) + && _checkVersion(filters, _version) + && _checkBuild(filters, _buildNumber) + && _checkExtensions(filters); + } + + + /** + * Removes and cleans up the notification bar from DOM + */ + function cleanNotificationBar() { + var $notificationBar = $('#notification-bar'); + if ($notificationBar.length > 0) { + $notificationBar.remove(); + } + } + + /** + * Displays the Notification Bar UI + * @param {object} msgObj - json object containing message info to be displayed + * + */ + function showNotification(msgObj) { + var $htmlContent = $(msgObj.html), + $notificationBarElement = $(NotificationBarHtml); + + // Remove any SCRIPT tag to avoid secuirity issues + $htmlContent.find('script').remove(); + + // Remove any STYLE tag to avoid styling impact on Brackets DOM + $htmlContent.find('style').remove(); + + cleanNotificationBar(); //Remove an already existing notification bar, if any + $notificationBarElement.prependTo(".content"); + + var $notificationBar = $('#notification-bar'), + $notificationContent = $notificationBar.find('.content-container'), + $closeIcon = $notificationBar.find('.close-icon'); + + $notificationContent.append($htmlContent); + HealthLogger.sendAnalyticsData("notification", msgObj.sequence, "shown"); + + // Click handlers on actionable elements + if ($closeIcon.length > 0) { + $closeIcon.click(function () { + cleanNotificationBar(); + PreferencesManager.setViewState("lastHandledNotificationNumber", msgObj.sequence); + HealthLogger.sendAnalyticsData("notification", msgObj.sequence, "dismissedByClose"); + }); + } + + if (msgObj.actionables) { + $(msgObj.actionables).click(function () { + cleanNotificationBar(); + PreferencesManager.setViewState("lastHandledNotificationNumber", msgObj.sequence); + HealthLogger.sendAnalyticsData("notification", msgObj.sequence, "dismissedBy" + this.id); + }); + } + } + + + AppInit.appReady(function () { + checkForNotification(); + }); + + // For unit tests only + exports.checkForNotification = checkForNotification; +}); diff --git a/src/extensions/default/InAppNotifications/styles/styles.css b/src/extensions/default/InAppNotifications/styles/styles.css new file mode 100644 index 00000000000..fd843aacb79 --- /dev/null +++ b/src/extensions/default/InAppNotifications/styles/styles.css @@ -0,0 +1,56 @@ +#notification-bar { + display: block; + background-color: #105F9C; + box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.53); + padding: 5px 0px; + width: 100%; + min-height: 39px; + position: absolute; + z-index: 16; + left: 0px; + bottom: 25px; + outline: none; + overflow: hidden; + color: rgb(51, 51, 51); + background: rgb(223, 226, 226); +} + +.dark #notification-bar { + color: #ccc; + background: #2c2c2c; +} + +#notification-bar .content-container { + padding: 5px 10px; + float: left; + width: 100%; +} + +#notification-bar .close-icon-container { + height: auto; + position: absolute; + float: right; + text-align: center; + width: auto; + min-width: 66px; + right: 20px; + top: 10px; +} + +#notification-bar .close-icon-container .close-icon { + display: block; + font-size: 18px; + line-height: 18px; + text-decoration: none; + width: 18px; + height: 18px; + background-color: transparent; + border: none; + padding: 0px; /*This is needed to center the icon*/ + float: right; +} + +.dark #notification-bar .close-icon-container .close-icon { + color: #ccc; +} + From ab67329daf9b76ea47658ec1829fed022bf722e8 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Thu, 2 May 2019 13:40:42 +0530 Subject: [PATCH 097/149] Update brackets version on master --- package.json | 4 ++-- src/config.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 39a007375b3..805dff57463 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Brackets", - "version": "1.14.0-0", - "apiVersion": "1.14.0", + "version": "1.15.0-0", + "apiVersion": "1.15.0", "homepage": "http://brackets.io", "issues": { "url": "http://github.com/adobe/brackets/issues" diff --git a/src/config.json b/src/config.json index 016a404abb7..cc1c874f73f 100644 --- a/src/config.json +++ b/src/config.json @@ -26,8 +26,8 @@ "update_info_url": "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json" }, "name": "Brackets", - "version": "1.14.0-0", - "apiVersion": "1.14.0", + "version": "1.15.0-0", + "apiVersion": "1.15.0", "homepage": "http://brackets.io", "issues": { "url": "http://github.com/adobe/brackets/issues" From 26b90b4d8c82340c459675734f1e13c132f365c4 Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Tue, 21 May 2019 14:10:24 +0530 Subject: [PATCH 098/149] Fix Extension Filters --- .../default/InAppNotifications/main.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/InAppNotifications/main.js b/src/extensions/default/InAppNotifications/main.js index f51684006cb..c49768f5dc2 100644 --- a/src/extensions/default/InAppNotifications/main.js +++ b/src/extensions/default/InAppNotifications/main.js @@ -213,6 +213,12 @@ define(function (require, exports, module) { } function _checkExtensions(filters) { + //if no property called extensions then it's a universal notification + if (filters.extensions === undefined) { + console.log("undefined"); + return true; + } + var allExtensions = ExtensionManager.extensions, allExtnsMatched = true, userExtensionKeys = Object.keys(allExtensions).filter(function(k) { @@ -220,10 +226,18 @@ define(function (require, exports, module) { }); if (!filters.extensions) { - allExtnsMatched = userExtensionKeys.size === 0; + console.log("null"); + //if property called extensions exists but has a falsy value + //then number of user extensions must be zero + allExtnsMatched = userExtensionKeys.length === 0; } else if (filters.extensions.length === 0) { + console.log('[]'); + //if property called extensions exists but is an empty array + //then number of user extensions must greater than zero allExtnsMatched = userExtensionKeys.length > 0; } else { + //if property called extensions exists but is a non empty array + //then notification is targetted to users having the fitered extensions var filteredExtns = filters.extensions, extnIterator = null; for (var i=0; i < filteredExtns.length; i++) { From a4784f5e89feb051e989849c67a41c81f4276aab Mon Sep 17 00:00:00 2001 From: Shubham Yadav Date: Tue, 21 May 2019 14:11:31 +0530 Subject: [PATCH 099/149] Remove console logs --- src/extensions/default/InAppNotifications/main.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/extensions/default/InAppNotifications/main.js b/src/extensions/default/InAppNotifications/main.js index c49768f5dc2..b4c2e582c96 100644 --- a/src/extensions/default/InAppNotifications/main.js +++ b/src/extensions/default/InAppNotifications/main.js @@ -215,7 +215,6 @@ define(function (require, exports, module) { function _checkExtensions(filters) { //if no property called extensions then it's a universal notification if (filters.extensions === undefined) { - console.log("undefined"); return true; } @@ -226,12 +225,10 @@ define(function (require, exports, module) { }); if (!filters.extensions) { - console.log("null"); //if property called extensions exists but has a falsy value //then number of user extensions must be zero allExtnsMatched = userExtensionKeys.length === 0; } else if (filters.extensions.length === 0) { - console.log('[]'); //if property called extensions exists but is an empty array //then number of user extensions must greater than zero allExtnsMatched = userExtensionKeys.length > 0; From ce2b870fd92e40c065297f5c8e5be6a40ad9c72a Mon Sep 17 00:00:00 2001 From: Gautam Jha Date: Mon, 11 Nov 2019 15:25:19 +0530 Subject: [PATCH 100/149] Adding infobar for remote debugging enabled (#14956) * Adding infobar for remote debugging enabled * Changing infobar type to warning * Removed button related codes * infobar is an independent unit * Changed getRemoteDebuggingPort usage * Addressing review comments * Updating infobar message --- src/brackets.js | 10 ++ src/document/DocumentCommandHandlers.js | 47 +++--- src/htmlContent/infobar-template.html | 13 ++ src/nls/root/strings.js | 5 +- src/styles/brackets_shared.less | 2 + src/styles/images/infobar-alert.svg | 10 ++ src/styles/images/infobar-checkmarkcircle.svg | 10 ++ src/styles/images/infobar-info.svg | 10 ++ src/styles/infobar-styles.less | 158 ++++++++++++++++++ src/widgets/infobar.js | 135 +++++++++++++++ 10 files changed, 377 insertions(+), 23 deletions(-) create mode 100644 src/htmlContent/infobar-template.html create mode 100644 src/styles/images/infobar-alert.svg create mode 100644 src/styles/images/infobar-checkmarkcircle.svg create mode 100644 src/styles/images/infobar-info.svg create mode 100644 src/styles/infobar-styles.less create mode 100644 src/widgets/infobar.js diff --git a/src/brackets.js b/src/brackets.js index 27c543879c9..7629622cfd7 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -257,6 +257,16 @@ define(function (require, exports, module) { ); } + brackets.app.getRemoteDebuggingPort(function (err, remote_debugging_port){ + if (remote_debugging_port && remote_debugging_port > 0) { + var InfoBar = require('widgets/infobar'); + InfoBar.showInfoBar({ + type: "warning", + title: `${Strings.REMOTE_DEBUGGING_ENABLED} ${remote_debugging_port}`, + description: "" + }); + } + }); // Use quiet scrollbars if we aren't on Lion. If we're on Lion, only // use native scroll bars when the mouse is not plugged in or when // using the "Always" scroll bar setting. diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index ed875376d9f..bb0889d1844 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -1642,28 +1642,31 @@ define(function (require, exports, module) { if (brackets.inBrowser) { result.resolve(); } else { - var port = brackets.app.getRemoteDebuggingPort ? brackets.app.getRemoteDebuggingPort() : 9234; - Inspector.getDebuggableWindows("127.0.0.1", port) - .fail(result.reject) - .done(function (response) { - var page = response[0]; - if (!page || !page.webSocketDebuggerUrl) { - result.reject(); - return; - } - var _socket = new WebSocket(page.webSocketDebuggerUrl); - // Disable the cache - _socket.onopen = function _onConnect() { - _socket.send(JSON.stringify({ id: 1, method: "Network.setCacheDisabled", params: { "cacheDisabled": true } })); - }; - // The first message will be the confirmation => disconnected to allow remote debugging of Brackets - _socket.onmessage = function _onMessage(e) { - _socket.close(); - result.resolve(); - }; - // In case of an error - _socket.onerror = result.reject; - }); + brackets.app.getRemoteDebuggingPort(function (err, port){ + if (port && port > 0) { + Inspector.getDebuggableWindows("127.0.0.1", port) + .fail(result.reject) + .done(function (response) { + var page = response[0]; + if (!page || !page.webSocketDebuggerUrl) { + result.reject(); + return; + } + var _socket = new WebSocket(page.webSocketDebuggerUrl); + // Disable the cache + _socket.onopen = function _onConnect() { + _socket.send(JSON.stringify({ id: 1, method: "Network.setCacheDisabled", params: { "cacheDisabled": true } })); + }; + // The first message will be the confirmation => disconnected to allow remote debugging of Brackets + _socket.onmessage = function _onMessage(e) { + _socket.close(); + result.resolve(); + }; + // In case of an error + _socket.onerror = result.reject; + }); + } + }); } return result.promise(); diff --git a/src/htmlContent/infobar-template.html b/src/htmlContent/infobar-template.html new file mode 100644 index 00000000000..ab73c367b52 --- /dev/null +++ b/src/htmlContent/infobar-template.html @@ -0,0 +1,13 @@ +
    +
    + +
    +
    +

    {{title}}  {{{description}}}

    +
    + {{^buttons}} +
    + +
    + {{/buttons}} +
    \ No newline at end of file diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index dc74e2f354c..7f7646b8804 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -897,5 +897,8 @@ define({ "REFERENCES_NO_RESULTS" : "No References available for current cursor position", "CMD_FIND_DOCUMENT_SYMBOLS" : "Find Document Symbols", - "CMD_FIND_PROJECT_SYMBOLS" : "Find Project Symbols" + "CMD_FIND_PROJECT_SYMBOLS" : "Find Project Symbols", + + // Remote debugging enabled + "REMOTE_DEBUGGING_ENABLED" : "Remote debugging enabled on localhost:" }); diff --git a/src/styles/brackets_shared.less b/src/styles/brackets_shared.less index 7c7dd2cabcf..94665a160d8 100644 --- a/src/styles/brackets_shared.less +++ b/src/styles/brackets_shared.less @@ -57,3 +57,5 @@ // Styling for scrollbars @import url("brackets_scrollbars.less"); + +@import url("infobar-styles.less"); diff --git a/src/styles/images/infobar-alert.svg b/src/styles/images/infobar-alert.svg new file mode 100644 index 00000000000..2390583d92f --- /dev/null +++ b/src/styles/images/infobar-alert.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/styles/images/infobar-checkmarkcircle.svg b/src/styles/images/infobar-checkmarkcircle.svg new file mode 100644 index 00000000000..fc0706d5d99 --- /dev/null +++ b/src/styles/images/infobar-checkmarkcircle.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/styles/images/infobar-info.svg b/src/styles/images/infobar-info.svg new file mode 100644 index 00000000000..5b23b6491fd --- /dev/null +++ b/src/styles/images/infobar-info.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/styles/infobar-styles.less b/src/styles/infobar-styles.less new file mode 100644 index 00000000000..246cda67b77 --- /dev/null +++ b/src/styles/infobar-styles.less @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019 - present 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. + * + */ + +/*info Bar*/ +#info-bar-template { + display: block; + background-color: #105F9C; + box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.53); + height: 38px; + width: 100%; + position: absolute; + z-index: 15; + left: 0px; + bottom: 25px; + outline: none; + overflow: hidden; +} + +#info-bar-template #icon-container { + width: auto; + height: auto; + padding: 11px; + float: left; +} +#info-bar-template #icon-container #info-icon { + background: url("images/infobar-info.svg") no-repeat 0 0; + width: 16px; + height: 16px; + display: block; +} + +#info-bar-template #content-container { + padding: 10px 7px; + float: left; + max-width: 78%; +} + +#info-bar-template #content-container #info-content { + margin: 0px !important; /*Check if this important is necessary*/ + line-height: 18px; + font-size: 14px; + font-family: 'SourceSansPro'; + color: #FFFFFF; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#info-bar-template #content-container #info-content #heading{ + font-weight: bold; +} +/*For focussed link of brackets.io*/ +#info-bar-template #content-container #info-content #description a:focus{ + box-shadow: none; +} + +#info-bar-template #content-container #info-content #description a{ + text-decoration: underline; + color: #FFFFFF; +} + +#info-bar-template #close-icon-container { + height: auto; + padding: 9px; + position: fixed; + float: right; + text-align: center; + width: auto; + min-width: 66px; + right: 30px; + background-color: #105F9C; +} + +#info-bar-template #close-icon-container #close-icon { + display: block; + color: white; + font-size: 18px; + line-height: 18px; + text-decoration: none; + width: 18px; + height: 18px; + background-color: transparent; + border: none; + padding: 0px; /*This is needed to center the icon*/ + float: right; +} + +#info-bar-template #close-icon-container #close-icon:hover { + background-color: rgba(255, 255, 255 ,0.16); + border-radius: 50%; +} + +#info-bar-template #close-icon-container #close-icon:focus { + background-color: rgba(255, 255, 255 ,0.16); + border-radius: 50%; + border: 1px solid #C3E3FF; + outline: 0; +} + +#info-bar-template #close-icon-container #close-icon:focus:active { + background-color: rgba(255, 255, 255 ,0.32); + border: none; +} + +/*Warning Message in info Bar*/ +#info-bar-template.warning, #info-bar-template.warning #close-icon-container { + background-color: #DA7A12; +} + +.dark #info-bar-template.warning, .dark #info-bar-template.warning #close-icon-container { + background-color: #E6851A; +} + +#info-bar-template.warning #icon-container #info-icon, +#info-bar-template.error #icon-container #info-icon { + background: url("images/infobar-alert.svg") no-repeat 0 0; +} + +/*Error message in info Bar*/ +#info-bar-template.error, #info-bar-template.error #close-icon-container { + background-color: #D7373F; +} + +.dark #info-bar-template.error, .dark #info-bar-template.error #close-icon-container{ + background-color: #E4484F; +} +/*Success message in info Bar*/ +#info-bar-template.success, #info-bar-template.success #close-icon-container { + background-color: #278E6B; +} + +.dark #info-bar-template.success, .dark #info-bar-template.success #close-icon-container { + background-color: #2E9D77; +} + +#info-bar-template.success #icon-container #info-icon{ + background: url("images/infobar-checkmarkcircle.svg") no-repeat 0 0; +} diff --git a/src/widgets/infobar.js b/src/widgets/infobar.js new file mode 100644 index 00000000000..4a266d63332 --- /dev/null +++ b/src/widgets/infobar.js @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + var MainViewManager = require("view/MainViewManager"), + Mustache = require("thirdparty/mustache/mustache"), + EventDispatcher = require("utils/EventDispatcher"), + InfoBarHtml = require("text!htmlContent/infobar-template.html"), + _ = require("thirdparty/lodash"); + + EventDispatcher.makeEventDispatcher(exports); + + // Key handlers for buttons in UI + var ESC_KEY = 27; // keycode for escape key + + /** + * Generates the json to be used by Mustache for rendering + * @param {object} msgObj - json object containing message information to be displayed + * @returns {object} - the generated json object + */ + function generateJsonForMustache(msgObj) { + var msgJsonObj = {}; + if (msgObj.type) { + msgJsonObj.type = "'" + msgObj.type + "'"; + } + msgJsonObj.title = msgObj.title; + msgJsonObj.description = msgObj.description; + return msgJsonObj; + } + /** + * Removes and cleans up the info bar from DOM + */ + function cleanInfoBar() { + var $infoBar = $('#info-bar-template'); + if ($infoBar.length > 0) { + $infoBar.remove(); + } + $(window.document).off("keydown.InfoBarTemplateDoc"); + $(window).off('resize.InfoBarTemplate'); + } + + /** + * Displays the Info Bar UI + * @param {object} msgObj - json object containing message info to be displayed + * + */ + function showInfoBar(msgObj) { + var jsonToMustache = generateJsonForMustache(msgObj), + $infoBarElement = $(Mustache.render(InfoBarHtml, jsonToMustache)); + + cleanInfoBar(); //Remove an already existing info bar, if any + $infoBarElement.prependTo(".content"); + + var $infoBar = $('#info-bar-template'), + $infoContent = $infoBar.find('#info-content'), + $contentContainer = $infoBar.find('#content-container'), + $iconContainer = $infoBar.find('#icon-container'), + $closeIconContainer = $infoBar.find('#close-icon-container'), + $heading = $infoBar.find('#heading'), + $description = $infoBar.find('#description'), + $closeIcon = $infoBar.find('#close-icon'); + + if ($infoContent.length > 0) { + if ($infoContent[0].scrollWidth > $infoContent.innerWidth()) { + //Text has over-flown, show the info content as tooltip message + if ($contentContainer.length > 0 && + $heading.length > 0 && + $description.length > 0) { + $contentContainer.attr("title", $heading.text() + $description.text()); + } + } + } + // Content Container Width between Icon Container and Button Container or Close Icon Container + // will be assigned when window will be rezied. + var resizeContentContainer = function () { + if($infoContent.length > 0 && $contentContainer.length > 0 && $infoBar.length > 0) { + var newWidth = $infoBar.outerWidth() - 38; + if($iconContainer.length > 0) { + newWidth = newWidth - $iconContainer.outerWidth(); + } + if($closeIconContainer.length > 0) { + newWidth = newWidth - $closeIconContainer.outerWidth(); + } + + $contentContainer.css({ + "maxWidth": newWidth + }); + } + }; + + resizeContentContainer(); + $(window).on('resize.InfoBarTemplate', _.debounce(resizeContentContainer, 150)); + + //Event handlers on the Info Bar + // Click and key handlers on Close button + if ($closeIcon.length > 0) { + $closeIcon.click(function () { + cleanInfoBar(); + MainViewManager.focusActivePane(); + }); + } + $(window.document).on("keydown.InfoBarTemplateDoc", function (event) { + var code = event.which; + if (code === ESC_KEY) { + // Keyboard input of Esc key on Info Bar dismisses and removes the bar + cleanInfoBar(); + MainViewManager.focusActivePane(); + event.stopImmediatePropagation(); + } + }); + } + exports.showInfoBar = showInfoBar; +}); From 2e886a35aa7a155801a7b3683ed697fe1d41d81d Mon Sep 17 00:00:00 2001 From: Gautam Jha Date: Mon, 11 Nov 2019 20:06:37 +0530 Subject: [PATCH 101/149] Removed node_modules test dirs having archives for MacOS notarization (#14954) * Removed archives from node_modules test dir for MacOS notarization * Adding a newline at end of file * Reverting my earlier fix to force php 7 in travis No longer required since 7.2 is the default php version. 7.0 is no longer supported in travis, which is causing travis build to fail. --- .travis.yml | 2 -- Gruntfile.js | 14 +++++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2803e1d928..464408502fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: node_js sudo: false # use container-based Travis infrastructure node_js: - "6" -before_install: - - phpenv global 7.0 #switch to php7, since that's what php-Tooling extension requires before_script: - npm install -g grunt-cli - npm install -g jasmine-node diff --git a/Gruntfile.js b/Gruntfile.js index 0a276991b8c..b6598f4e3af 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -50,6 +50,16 @@ module.exports = function (grunt) { 'src/styles/brackets.css' ] }] + }, + node_modules_test_dir : { + files: [{ + dot: true, + src: [ + 'dist/node_modules/npm/test/fixtures', + 'dist/node_modules/npm/node_modules/tar/test', + 'dist/node_modules/npm/node_modules/npm-registry-client/test' + ] + }] } }, copy: { @@ -412,7 +422,8 @@ module.exports = function (grunt) { 'npm-install', 'cleanempty', 'usemin', - 'build-config' + 'build-config', + 'clean:node_modules_test_dir' ]); // task: build @@ -430,3 +441,4 @@ module.exports = function (grunt) { // Default task. grunt.registerTask('default', ['test']); }; + From 66ff4fa4645867b1eafcc767a549fc41d50c0dd1 Mon Sep 17 00:00:00 2001 From: Gautam Jha Date: Tue, 12 Nov 2019 20:14:18 +0530 Subject: [PATCH 102/149] Remove space between localhost: and port in infobar message (#14961) --- src/brackets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brackets.js b/src/brackets.js index 7629622cfd7..31fad94ef82 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -262,7 +262,7 @@ define(function (require, exports, module) { var InfoBar = require('widgets/infobar'); InfoBar.showInfoBar({ type: "warning", - title: `${Strings.REMOTE_DEBUGGING_ENABLED} ${remote_debugging_port}`, + title: `${Strings.REMOTE_DEBUGGING_ENABLED}${remote_debugging_port}`, description: "" }); } From afc4f8db9e107ba5af9d93d9d14021f501feec1d Mon Sep 17 00:00:00 2001 From: walf Date: Wed, 13 Nov 2019 00:43:54 -0800 Subject: [PATCH 103/149] Updated by ALF automation. --- src/nls/fr/strings.js | 5 ++++- src/nls/ja/strings.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 866b74cba2f..6d2381e14c7 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -897,5 +897,8 @@ define({ "REFERENCES_NO_RESULTS": "Références non disponibles pour la position actuelle du curseur", "CMD_FIND_DOCUMENT_SYMBOLS": "Rechercher des symboles de document", - "CMD_FIND_PROJECT_SYMBOLS": "Rechercher des symboles de projet" + "CMD_FIND_PROJECT_SYMBOLS": "Rechercher des symboles de projet", + + // Remote debugging enabled + "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 5a2a88733c7..c0474fbe7fb 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -897,5 +897,8 @@ define({ "REFERENCES_NO_RESULTS": "現在のカーソル位置で利用可能な参照はありません", "CMD_FIND_DOCUMENT_SYMBOLS": "ドキュメント記号を検索", - "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索" + "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索", + + // Remote debugging enabled + "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:" }); From 38de77861c72275f0309ca011aa158f8174adb80 Mon Sep 17 00:00:00 2001 From: Gautam Jha Date: Wed, 20 Nov 2019 15:41:37 +0530 Subject: [PATCH 104/149] Finalizing strings for gkit (#14976) --- src/nls/root/strings.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7f7646b8804..bb7c0d42698 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -900,5 +900,8 @@ define({ "CMD_FIND_PROJECT_SYMBOLS" : "Find Project Symbols", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED" : "Remote debugging enabled on localhost:" + "REMOTE_DEBUGGING_ENABLED" : "Remote debugging enabled on localhost:", + + // Remote debugging port argument is invalid + "REMOTE_DEBUGGING_PORT_INVALID" : "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}." }); From 18897afc976ff50e4ca9bf5b4995875979815362 Mon Sep 17 00:00:00 2001 From: walf Date: Wed, 20 Nov 2019 09:55:00 -0800 Subject: [PATCH 105/149] Updated by ALF automation. --- src/nls/fr/strings.js | 5 ++++- src/nls/ja/strings.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 6d2381e14c7..5e5052b0028 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -900,5 +900,8 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "Rechercher des symboles de projet", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:" + "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:", + + // Remote debugging port argument is invalid + "REMOTE_DEBUGGING_PORT_INVALID": "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}." }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index c0474fbe7fb..6031953e5da 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -900,5 +900,8 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:" + "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:", + + // Remote debugging port argument is invalid + "REMOTE_DEBUGGING_PORT_INVALID": "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}." }); From 8a806ec41b613d70b26005e2c4907b021a41e744 Mon Sep 17 00:00:00 2001 From: Gautam Jha Date: Thu, 21 Nov 2019 11:16:14 +0530 Subject: [PATCH 106/149] Moving command line port validation errors to info bar (#14977) --- src/brackets.js | 11 +++++++++-- src/document/DocumentCommandHandlers.js | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/brackets.js b/src/brackets.js index 31fad94ef82..c291a32216e 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -258,13 +258,20 @@ define(function (require, exports, module) { } brackets.app.getRemoteDebuggingPort(function (err, remote_debugging_port){ - if (remote_debugging_port && remote_debugging_port > 0) { - var InfoBar = require('widgets/infobar'); + var InfoBar = require('widgets/infobar'), + StringUtils = require("utils/StringUtils"); + if ((!err) && remote_debugging_port && remote_debugging_port > 0) { InfoBar.showInfoBar({ type: "warning", title: `${Strings.REMOTE_DEBUGGING_ENABLED}${remote_debugging_port}`, description: "" }); + } else if (err) { + InfoBar.showInfoBar({ + type: "error", + title: StringUtils.format(Strings.REMOTE_DEBUGGING_PORT_INVALID, err, 1024, 65534), + description: "" + }); } }); // Use quiet scrollbars if we aren't on Lion. If we're on Lion, only diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index bb0889d1844..7d7614d2dc8 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -1643,7 +1643,7 @@ define(function (require, exports, module) { result.resolve(); } else { brackets.app.getRemoteDebuggingPort(function (err, port){ - if (port && port > 0) { + if ((!err) && port && port > 0) { Inspector.getDebuggableWindows("127.0.0.1", port) .fail(result.reject) .done(function (response) { From 962b30f85eac56e42c5adeffbb84758883b1dd72 Mon Sep 17 00:00:00 2001 From: walf Date: Thu, 21 Nov 2019 09:55:08 -0800 Subject: [PATCH 107/149] Updated by ALF automation. --- src/nls/fr/strings.js | 4 ++-- src/nls/ja/strings.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 5e5052b0028..c3949fc01f1 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -900,8 +900,8 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "Rechercher des symboles de projet", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:", + "REMOTE_DEBUGGING_ENABLED": "Débogage à distance activé sur localhost :", // Remote debugging port argument is invalid - "REMOTE_DEBUGGING_PORT_INVALID": "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}." + "REMOTE_DEBUGGING_PORT_INVALID": "Impossible d’activer le débogage à distance sur le port {0}. Les numéros de port doivent être compris entre {1} et {2}." }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 6031953e5da..51f49c4fa9a 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -900,8 +900,8 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "Remote debugging enabled on localhost:", + "REMOTE_DEBUGGING_ENABLED": "次のローカルホストでリモートデバッグが有効になりました。", // Remote debugging port argument is invalid - "REMOTE_DEBUGGING_PORT_INVALID": "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}." + "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。" }); From f182c32019b9a93915d53b540e3ec40017d1748f Mon Sep 17 00:00:00 2001 From: walf Date: Tue, 26 Nov 2019 03:31:24 -0800 Subject: [PATCH 108/149] Updated by ALF automation. --- src/nls/fr/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index c3949fc01f1..abf25a4580d 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -900,7 +900,7 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "Rechercher des symboles de projet", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "Débogage à distance activé sur localhost :", + "REMOTE_DEBUGGING_ENABLED": "Débogage à distance activé sur localhost:", // Remote debugging port argument is invalid "REMOTE_DEBUGGING_PORT_INVALID": "Impossible d’activer le débogage à distance sur le port {0}. Les numéros de port doivent être compris entre {1} et {2}." From 4803515e9a7ded11edda51852aae029eafd6111b Mon Sep 17 00:00:00 2001 From: walf Date: Wed, 27 Nov 2019 00:33:44 -0800 Subject: [PATCH 109/149] Updated by ALF automation. --- src/nls/ja/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 51f49c4fa9a..81b9f686cd4 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -900,7 +900,7 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "次のローカルホストでリモートデバッグが有効になりました。", + "REMOTE_DEBUGGING_ENABLED": "次の localhost でリモートデバッグが有効になりました。", // Remote debugging port argument is invalid "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。" From 8f26cd850e648d6c4dd04cdfa69119a7feda0867 Mon Sep 17 00:00:00 2001 From: walf Date: Wed, 27 Nov 2019 20:28:44 -0800 Subject: [PATCH 110/149] Updated by ALF automation. --- src/nls/ja/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 81b9f686cd4..f3e237698ad 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -900,7 +900,7 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "次の localhost でリモートデバッグが有効になりました。", + "REMOTE_DEBUGGING_ENABLED": "次のローカルホストでリモートデバッグが有効になりました : localhost:", // Remote debugging port argument is invalid "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。" From 95626cc3650dcd00e886670d80307b8f710d6168 Mon Sep 17 00:00:00 2001 From: walf Date: Wed, 27 Nov 2019 23:30:30 -0800 Subject: [PATCH 111/149] Updated by ALF automation. --- src/nls/ja/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index f3e237698ad..c4453363e6e 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -900,7 +900,7 @@ define({ "CMD_FIND_PROJECT_SYMBOLS": "プロジェクト記号を検索", // Remote debugging enabled - "REMOTE_DEBUGGING_ENABLED": "次のローカルホストでリモートデバッグが有効になりました : localhost:", + "REMOTE_DEBUGGING_ENABLED": "次のローカルホストでリモートデバッグが有効になりました。localhost:", // Remote debugging port argument is invalid "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。" From 03ddf9f3152032f18f234ce26e2643179db7f9e3 Mon Sep 17 00:00:00 2001 From: Gautam Jha Date: Thu, 5 Dec 2019 17:48:19 +0530 Subject: [PATCH 112/149] Fixing update notification locale issue, update notification was shown in English for all locales --- src/utils/UpdateNotification.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/utils/UpdateNotification.js b/src/utils/UpdateNotification.js index ed0ec018a00..6d31cd4d4ac 100644 --- a/src/utils/UpdateNotification.js +++ b/src/utils/UpdateNotification.js @@ -115,32 +115,6 @@ define(function (require, exports, module) { locale = locale.substring(0, 2); } - //AUTOUPDATE_PRERELEASE_BEGIN - // The following code is needed for supporting Auto Update in prerelease, - //and will be removed eventually for stable releases - { - if (locale) { - if(locale.length > 2) { - locale = locale.substring(0, 2); - } - switch(locale) { - case "de": - break; - case "es": - break; - case "fr": - break; - case "ja": - break; - case "en": - default: - locale = "en"; - } - return brackets.config.update_info_url.replace("", locale); - } - } - //AUTOUPDATE_PRERELEASE_END - return brackets.config.update_info_url + '?locale=' + locale; } From e0ea84ebb6c331eca2882b0c6ba3ae022e53b524 Mon Sep 17 00:00:00 2001 From: Gautam Jha Date: Fri, 6 Dec 2019 14:29:01 +0530 Subject: [PATCH 113/149] Addressing review comments --- src/brackets.config.dist.json | 2 +- src/utils/UpdateNotification.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/brackets.config.dist.json b/src/brackets.config.dist.json index c3411b31a28..e6168390ade 100644 --- a/src/brackets.config.dist.json +++ b/src/brackets.config.dist.json @@ -3,7 +3,7 @@ "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", "serviceKey" : "brackets-service", "environment" : "production", - "update_info_url" : "https://getupdates.brackets.io/getupdates/", + "update_info_url" : "https://getupdates.brackets.io/getupdates?locale=", "notification_info_url" : "https://getupdates.brackets.io/getnotifications?locale=", "buildtype" : "production" } diff --git a/src/utils/UpdateNotification.js b/src/utils/UpdateNotification.js index 6d31cd4d4ac..262dc8e6f89 100644 --- a/src/utils/UpdateNotification.js +++ b/src/utils/UpdateNotification.js @@ -115,7 +115,7 @@ define(function (require, exports, module) { locale = locale.substring(0, 2); } - return brackets.config.update_info_url + '?locale=' + locale; + return brackets.config.update_info_url.replace('', locale || 'en'); } /** From 75d3947af25abaf0e2c68401cb478e00ad14cd28 Mon Sep 17 00:00:00 2001 From: Sietse Brouwer Date: Mon, 20 Jan 2020 16:22:07 +0100 Subject: [PATCH 114/149] Public methods for closing Search Results and References bottom panel The bottom panels 'Search Results' and 'References' can only be closed in the UI at the moment, by clicking at the close button. Public methods for closing them enables extensions to attach a keyboard shortcut to this action. --- src/features/FindReferencesManager.js | 11 +++++++++++ src/search/FindInFilesUI.js | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/features/FindReferencesManager.js b/src/features/FindReferencesManager.js index 50b8cdfe52e..8b9470a959c 100644 --- a/src/features/FindReferencesManager.js +++ b/src/features/FindReferencesManager.js @@ -121,6 +121,16 @@ define(function (require, exports, module) { searchModel.clear(); } + /** + * @public + * Closes the references panel + */ + function closeReferencesPanel() { + if (_resultsView) { + _resultsView.close(); + } + } + function setMenuItemStateForLanguage(languageId) { CommandManager.get(Commands.CMD_FIND_ALL_REFERENCES).setEnabled(false); if (!languageId) { @@ -207,4 +217,5 @@ define(function (require, exports, module) { exports.registerFindReferencesProvider = registerFindReferencesProvider; exports.removeFindReferencesProvider = removeFindReferencesProvider; exports.setMenuItemStateForLanguage = setMenuItemStateForLanguage; + exports.closeReferencesPanel = closeReferencesPanel; }); diff --git a/src/search/FindInFilesUI.js b/src/search/FindInFilesUI.js index 4633fe326bf..7d1cc046216 100644 --- a/src/search/FindInFilesUI.js +++ b/src/search/FindInFilesUI.js @@ -450,6 +450,15 @@ define(function (require, exports, module) { } } + /** + * @public + * Closes the search results panel + */ + function closeResultsPanel() { + _resultsView.close(); + _closeFindBar(); + } + // Initialize items dependent on HTML DOM AppInit.htmlReady(function () { var model = FindInFiles.searchModel; @@ -495,6 +504,7 @@ define(function (require, exports, module) { // Public exports exports.searchAndShowResults = searchAndShowResults; exports.searchAndReplaceResults = searchAndReplaceResults; + exports.closeResultsPanel = closeResultsPanel; // For unit testing exports._showFindBar = _showFindBar; From f517012932972d2061cf76ca6232c796767a5290 Mon Sep 17 00:00:00 2001 From: Sietse Brouwer Date: Mon, 20 Jan 2020 16:22:40 +0100 Subject: [PATCH 115/149] Fix for `Reload With(out) Extensions` not working In Brackets 1.14.1 a browser reload failed (menu items Debug | Reload With(out) Extensions). Rejecting the `_disableCache()` promise when no remote debugging port is found, will fix this. --- src/document/DocumentCommandHandlers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index 7d7614d2dc8..bcc235f9a62 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -1665,6 +1665,8 @@ define(function (require, exports, module) { // In case of an error _socket.onerror = result.reject; }); + } else { + result.reject(); } }); } From 764921c42c560cf89746049f17b15b5f17ed7197 Mon Sep 17 00:00:00 2001 From: Narayani M Date: Mon, 17 Feb 2020 10:57:59 +0530 Subject: [PATCH 116/149] Adding value "legacy" to justify-items and removing "unset" from justify-items and justify-self as per w3 org recommendations. --- src/extensions/default/CSSCodeHints/CSSProperties.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/CSSCodeHints/CSSProperties.json b/src/extensions/default/CSSCodeHints/CSSProperties.json index 6f0b91fa316..22ee1aa23bc 100644 --- a/src/extensions/default/CSSCodeHints/CSSProperties.json +++ b/src/extensions/default/CSSCodeHints/CSSProperties.json @@ -140,8 +140,8 @@ "image-resolution": {"values": ["from-image", "snap"]}, "isolation": {"values": ["auto", "isolate"]}, "justify-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between"]}, - "justify-items": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial", "unset"]}, - "justity-self": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial", "unset"]}, + "justify-items": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "legacy", "inherit", "initial"]}, + "justity-self": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial"]}, "left": {"values": ["auto", "inherit"]}, "letter-spacing": {"values": ["normal", "inherit"]}, "line-height": {"values": ["normal", "inherit"]}, From b3d7de58f426d971e78fa11f92dd32c1b546236c Mon Sep 17 00:00:00 2001 From: Narayani M Date: Wed, 19 Feb 2020 14:25:16 +0530 Subject: [PATCH 117/149] Spell correction for justify-self --- src/extensions/default/CSSCodeHints/CSSProperties.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/default/CSSCodeHints/CSSProperties.json b/src/extensions/default/CSSCodeHints/CSSProperties.json index 22ee1aa23bc..342b9875dc9 100644 --- a/src/extensions/default/CSSCodeHints/CSSProperties.json +++ b/src/extensions/default/CSSCodeHints/CSSProperties.json @@ -141,7 +141,7 @@ "isolation": {"values": ["auto", "isolate"]}, "justify-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between"]}, "justify-items": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "legacy", "inherit", "initial"]}, - "justity-self": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial"]}, + "justify-self": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial"]}, "left": {"values": ["auto", "inherit"]}, "letter-spacing": {"values": ["normal", "inherit"]}, "line-height": {"values": ["normal", "inherit"]}, From 5171c85c20ff7483d7233e65b6395da9e00d8010 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 5 Mar 2020 16:22:45 +0530 Subject: [PATCH 118/149] Associate an file tpye to external Application --- src/file/FileUtils.js | 29 +++++++++++++++++++++++++++++ src/nls/root/strings.js | 5 ++++- src/project/FileTreeView.js | 14 ++++++++++++-- src/project/FileViewController.js | 8 ++++++++ src/project/ProjectManager.js | 8 ++++++++ 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/file/FileUtils.js b/src/file/FileUtils.js index 308c389cd62..41c9c0f7998 100644 --- a/src/file/FileUtils.js +++ b/src/file/FileUtils.js @@ -59,6 +59,11 @@ define(function (require, exports, module) { */ var MAX_FILE_SIZE = MAX_FILE_SIZE_MB * 1024 * 1024; + /** + * @const {List} list of File Extensions which will be opened in external Application + */ + var extListToBeOpenedInExtApp = []; + /** * Asynchronously reads a file as UTF-8 encoded text. @@ -526,6 +531,28 @@ define(function (require, exports, module) { return pathArray.join("/"); } + /** + * @param {string} ext extension string a file + * @return {string} returns true If file to be opened in External Application. + * + */ + function shouldOpenInExternalApplication(ext) { + return !extListToBeOpenedInExtApp.includes(ext); + } + + /** + * @param {string} ext File Extensions to be added in External App List + * + */ + function addExtensionToExternalAppList(ext) { + + if(typeof ext !== 'string') { + extListToBeOpenedInExtApp.concat(ext); + } else { + extListToBeOpenedInExtApp.push(ext); + } + } + // Asynchronously load DocumentCommandHandlers // This avoids a temporary circular dependency created // by relocating showFileOpenError() until deprecation is over @@ -568,4 +595,6 @@ define(function (require, exports, module) { exports.comparePaths = comparePaths; exports.MAX_FILE_SIZE = MAX_FILE_SIZE; exports.encodeFilePath = encodeFilePath; + exports.shouldOpenInExternalApplication = shouldOpenInExternalApplication; + exports.addExtensionToExternalAppList = addExtensionToExternalAppList; }); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index bb7c0d42698..fbebdc49da1 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -903,5 +903,8 @@ define({ "REMOTE_DEBUGGING_ENABLED" : "Remote debugging enabled on localhost:", // Remote debugging port argument is invalid - "REMOTE_DEBUGGING_PORT_INVALID" : "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}." + "REMOTE_DEBUGGING_PORT_INVALID" : "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}.", + + //Associate File Type to External App + "DESCRIPTION_EXTERNAL_EDITOR" : "Add File type association to external App here" }); diff --git a/src/project/FileTreeView.js b/src/project/FileTreeView.js index 4efeef4a665..258444bd4c2 100644 --- a/src/project/FileTreeView.js +++ b/src/project/FileTreeView.js @@ -39,7 +39,8 @@ define(function (require, exports, module) { LanguageManager = require("language/LanguageManager"), FileTreeViewModel = require("project/FileTreeViewModel"), ViewUtils = require("utils/ViewUtils"), - KeyEvent = require("utils/KeyEvent"); + KeyEvent = require("utils/KeyEvent"), + PreferencesManager = require("preferences/PreferencesManager"); var DOM = Preact.DOM; @@ -554,7 +555,10 @@ define(function (require, exports, module) { }); } } else { - this.props.actions.setSelected(this.myPath()); + this.props.actions.setSelected(this.myPath(), + FileUtils.shouldOpenInExternalApplication( + FileUtils.getFileExtension(this.myPath()).toLowerCase() + )); } e.stopPropagation(); e.preventDefault(); @@ -569,6 +573,12 @@ define(function (require, exports, module) { if (this.state.clickTimer !== null) { this.clearTimer(); } + if (FileUtils.shouldOpenInExternalApplication( + FileUtils.getFileExtension(this.myPath()).toLowerCase() + )) { + this.props.actions.openInExternalEditor(this.myPath()); + return; + } this.props.actions.selectInWorkingSet(this.myPath()); } }, diff --git a/src/project/FileViewController.js b/src/project/FileViewController.js index e8079a0ebd5..fd1cb7886b8 100644 --- a/src/project/FileViewController.js +++ b/src/project/FileViewController.js @@ -226,6 +226,13 @@ define(function (require, exports, module) { return result.promise(); } + /** + * Opens the specified document with its associated external editor, + */ + function openInExternalEditor(fullPath) { + exports.trigger("openInExternalEditor", fullPath); + } + /** * Opens the specified document if it's not already open, adds it to the working set, * and selects it in the WorkingSetView @@ -275,4 +282,5 @@ define(function (require, exports, module) { exports.setFileViewFocus = setFileViewFocus; exports.WORKING_SET_VIEW = WORKING_SET_VIEW; exports.PROJECT_MANAGER = PROJECT_MANAGER; + exports.openInExternalEditor = openInExternalEditor; }); diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index db0cc7375f6..9a84336907f 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -280,6 +280,14 @@ define(function (require, exports, module) { this.model.selectInWorkingSet(path); }; + /** + * See `FileViewController.openInExternalEditor` + */ + ActionCreator.prototype.openInExternalEditor = function (path) { + FileViewController.openInExternalEditor(path); + }; + + /** * See `ProjectModel.setContext` */ From 0420ac1da3d7087e0833b7b1e8f27cedf58d65c7 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 5 Mar 2020 16:30:39 +0530 Subject: [PATCH 119/149] Adding missed files --- .../default/OpenWithExternalEditor/main.js | 70 +++++++++++++++++++ .../node/OpenWithExternalEditorDomain.js | 68 ++++++++++++++++++ .../OpenWithExternalEditor/node/package.json | 6 ++ 3 files changed, 144 insertions(+) create mode 100644 src/extensions/default/OpenWithExternalEditor/main.js create mode 100644 src/extensions/default/OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js create mode 100644 src/extensions/default/OpenWithExternalEditor/node/package.json diff --git a/src/extensions/default/OpenWithExternalEditor/main.js b/src/extensions/default/OpenWithExternalEditor/main.js new file mode 100644 index 00000000000..62c6766826b --- /dev/null +++ b/src/extensions/default/OpenWithExternalEditor/main.js @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + + var AppInit = brackets.getModule("utils/AppInit"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + Strings = brackets.getModule("strings"), + FileViewController = brackets.getModule("project/FileViewController"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + NodeDomain = brackets.getModule("utils/NodeDomain"), + FileUtils = brackets.getModule("file/FileUtils"); + + /** + * @private + * @type {string} fullPath of the OpenWithExternalEditor Domain implementation + */ + var _domainPath = ExtensionUtils.getModulePath(module, "node/OpenWithExternalEditorDomain"); + + /** + * @private + * @type {NodeDomain} + */ + var _nodeDomain = new NodeDomain("OpenWithExternalEditor", _domainPath); + + var extensionToExternalEditorMap = {}; + + function _openInExternalEdior(event, path) { + _nodeDomain.exec("open", { + path: path, + app: extensionToExternalEditorMap[FileUtils.getFileExtension(path).toLowerCase()] + }); + } + + PreferencesManager.definePreference("externalEditor", "object", {}, { + description: Strings.DESCRIPTION_EXTERNAL_EDITOR + }); + + PreferencesManager.on("change", "externalEditor", function () { + extensionToExternalEditorMap = PreferencesManager.get("externalEditor"); + }); + + FileViewController.on("openInExternalEditor", _openInExternalEdior); + + AppInit.appReady(function () { + FileUtils.addExtensionToExternalAppList(Object.keys(extensionToExternalEditorMap)); + }); +}); diff --git a/src/extensions/default/OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js b/src/extensions/default/OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js new file mode 100644 index 00000000000..4827cb3f3ed --- /dev/null +++ b/src/extensions/default/OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2012 - present 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. + * + */ + +/*eslint-env node */ +/*jslint node: true */ +"use strict"; + +var open = require("open"); + +var _domainManager; + +/** + * @private + * + * @param {Object} params Object to use + */ +function _OpenWithExternalEditor(params) { + var application = "default" === params.app ? "": params.app; + open(params.path, application); +} + + +/** + * Initializes the OpenWithExternalEditor domain with its commands. + * @param {DomainManager} domainManager The DomainManager for the server + */ +function init(domainManager) { + _domainManager = domainManager; + + if (!domainManager.hasDomain("OpenWithExternalEditor")) { + domainManager.registerDomain("OpenWithExternalEditor", {major: 0, minor: 1}); + } + _domainManager.registerCommand( + "OpenWithExternalEditor", + "open", + _OpenWithExternalEditor, + true, + "open document with External Editor.", + [{ + name: "params", + type: "object", + description: "Params Object having document and App Path." + }], + [] + ); +} + +exports.init = init; diff --git a/src/extensions/default/OpenWithExternalEditor/node/package.json b/src/extensions/default/OpenWithExternalEditor/node/package.json new file mode 100644 index 00000000000..178069e3c95 --- /dev/null +++ b/src/extensions/default/OpenWithExternalEditor/node/package.json @@ -0,0 +1,6 @@ +{ + "name": "brackets-open-external_editor", + "dependencies": { + "open": "0.0.5" + } +} From 7f5f0647d2df6315e1f5a5bf38e5433312f44959 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 5 Mar 2020 18:08:48 +0530 Subject: [PATCH 120/149] few corrections in naming conventions --- .../main.js | 24 ++++++++++--------- .../OpenWithExternalApplicationDomain.js} | 12 +++++----- .../node/package.json | 2 +- src/file/FileUtils.js | 4 ++-- src/nls/root/strings.js | 2 +- src/project/FileTreeView.js | 2 +- src/project/FileViewController.js | 6 ++--- src/project/ProjectManager.js | 6 ++--- 8 files changed, 30 insertions(+), 28 deletions(-) rename src/extensions/default/{OpenWithExternalEditor => OpenWithExternalApplication}/main.js (71%) rename src/extensions/default/{OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js => OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js} (85%) rename src/extensions/default/{OpenWithExternalEditor => OpenWithExternalApplication}/node/package.json (50%) diff --git a/src/extensions/default/OpenWithExternalEditor/main.js b/src/extensions/default/OpenWithExternalApplication/main.js similarity index 71% rename from src/extensions/default/OpenWithExternalEditor/main.js rename to src/extensions/default/OpenWithExternalApplication/main.js index 62c6766826b..535a6333ee7 100644 --- a/src/extensions/default/OpenWithExternalEditor/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -37,34 +37,36 @@ define(function (require, exports, module) { * @private * @type {string} fullPath of the OpenWithExternalEditor Domain implementation */ - var _domainPath = ExtensionUtils.getModulePath(module, "node/OpenWithExternalEditorDomain"); + var _domainPath = ExtensionUtils.getModulePath(module, "node/OpenWithExternalApplicationDomain"); /** * @private * @type {NodeDomain} */ - var _nodeDomain = new NodeDomain("OpenWithExternalEditor", _domainPath); + var _nodeDomain = new NodeDomain("OpenWithExternalApplication", _domainPath); - var extensionToExternalEditorMap = {}; + var extensionToExtApplicationMap = {}; - function _openInExternalEdior(event, path) { + function _openWithExternalApplication(event, path) { _nodeDomain.exec("open", { path: path, - app: extensionToExternalEditorMap[FileUtils.getFileExtension(path).toLowerCase()] + app: extensionToExtApplicationMap[FileUtils.getFileExtension(path).toLowerCase()] }); } - PreferencesManager.definePreference("externalEditor", "object", {}, { - description: Strings.DESCRIPTION_EXTERNAL_EDITOR + PreferencesManager.definePreference("externalApplications", "object", {}, { + description: Strings.DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE }); - PreferencesManager.on("change", "externalEditor", function () { - extensionToExternalEditorMap = PreferencesManager.get("externalEditor"); + PreferencesManager.on("change", "externalApplications", function () { + extensionToExtApplicationMap = PreferencesManager.get("externalApplications"); + FileUtils.addExtensionToExternalAppList(Object.keys(extensionToExtApplicationMap)); }); - FileViewController.on("openInExternalEditor", _openInExternalEdior); + FileViewController.on("openWithExternalApplication", _openWithExternalApplication); AppInit.appReady(function () { - FileUtils.addExtensionToExternalAppList(Object.keys(extensionToExternalEditorMap)); + extensionToExtApplicationMap = PreferencesManager.get("externalApplications"); + FileUtils.addExtensionToExternalAppList(Object.keys(extensionToExtApplicationMap)); }); }); diff --git a/src/extensions/default/OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js similarity index 85% rename from src/extensions/default/OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js rename to src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js index 4827cb3f3ed..8a864689ef6 100644 --- a/src/extensions/default/OpenWithExternalEditor/node/OpenWithExternalEditorDomain.js +++ b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js @@ -34,7 +34,7 @@ var _domainManager; * * @param {Object} params Object to use */ -function _OpenWithExternalEditor(params) { +function _openWithExternalApplication(params) { var application = "default" === params.app ? "": params.app; open(params.path, application); } @@ -47,15 +47,15 @@ function _OpenWithExternalEditor(params) { function init(domainManager) { _domainManager = domainManager; - if (!domainManager.hasDomain("OpenWithExternalEditor")) { - domainManager.registerDomain("OpenWithExternalEditor", {major: 0, minor: 1}); + if (!domainManager.hasDomain("OpenWithExternalApplication")) { + domainManager.registerDomain("OpenWithExternalApplication", {major: 0, minor: 1}); } _domainManager.registerCommand( - "OpenWithExternalEditor", + "OpenWithExternalApplication", "open", - _OpenWithExternalEditor, + _openWithExternalApplication, true, - "open document with External Editor.", + "open document with External Application.", [{ name: "params", type: "object", diff --git a/src/extensions/default/OpenWithExternalEditor/node/package.json b/src/extensions/default/OpenWithExternalApplication/node/package.json similarity index 50% rename from src/extensions/default/OpenWithExternalEditor/node/package.json rename to src/extensions/default/OpenWithExternalApplication/node/package.json index 178069e3c95..65bb049736e 100644 --- a/src/extensions/default/OpenWithExternalEditor/node/package.json +++ b/src/extensions/default/OpenWithExternalApplication/node/package.json @@ -1,5 +1,5 @@ { - "name": "brackets-open-external_editor", + "name": "brackets-open-external_application", "dependencies": { "open": "0.0.5" } diff --git a/src/file/FileUtils.js b/src/file/FileUtils.js index 41c9c0f7998..819168d12b4 100644 --- a/src/file/FileUtils.js +++ b/src/file/FileUtils.js @@ -537,7 +537,7 @@ define(function (require, exports, module) { * */ function shouldOpenInExternalApplication(ext) { - return !extListToBeOpenedInExtApp.includes(ext); + return extListToBeOpenedInExtApp.includes(ext); } /** @@ -547,7 +547,7 @@ define(function (require, exports, module) { function addExtensionToExternalAppList(ext) { if(typeof ext !== 'string') { - extListToBeOpenedInExtApp.concat(ext); + extListToBeOpenedInExtApp = ext; } else { extListToBeOpenedInExtApp.push(ext); } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index fbebdc49da1..e01b2300aeb 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -906,5 +906,5 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID" : "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}.", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_EDITOR" : "Add File type association to external App here" + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE" : "Add File type association to external App here" }); diff --git a/src/project/FileTreeView.js b/src/project/FileTreeView.js index 258444bd4c2..ec71c797664 100644 --- a/src/project/FileTreeView.js +++ b/src/project/FileTreeView.js @@ -576,7 +576,7 @@ define(function (require, exports, module) { if (FileUtils.shouldOpenInExternalApplication( FileUtils.getFileExtension(this.myPath()).toLowerCase() )) { - this.props.actions.openInExternalEditor(this.myPath()); + this.props.actions.openWithExternalApplication(this.myPath()); return; } this.props.actions.selectInWorkingSet(this.myPath()); diff --git a/src/project/FileViewController.js b/src/project/FileViewController.js index fd1cb7886b8..ac616e943c0 100644 --- a/src/project/FileViewController.js +++ b/src/project/FileViewController.js @@ -229,8 +229,8 @@ define(function (require, exports, module) { /** * Opens the specified document with its associated external editor, */ - function openInExternalEditor(fullPath) { - exports.trigger("openInExternalEditor", fullPath); + function openWithExternalApplication(fullPath) { + exports.trigger("openWithExternalApplication", fullPath); } /** @@ -282,5 +282,5 @@ define(function (require, exports, module) { exports.setFileViewFocus = setFileViewFocus; exports.WORKING_SET_VIEW = WORKING_SET_VIEW; exports.PROJECT_MANAGER = PROJECT_MANAGER; - exports.openInExternalEditor = openInExternalEditor; + exports.openWithExternalApplication = openWithExternalApplication; }); diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 9a84336907f..f6a799adf12 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -281,10 +281,10 @@ define(function (require, exports, module) { }; /** - * See `FileViewController.openInExternalEditor` + * See `FileViewController.openWithExternalApplication` */ - ActionCreator.prototype.openInExternalEditor = function (path) { - FileViewController.openInExternalEditor(path); + ActionCreator.prototype.openWithExternalApplication = function (path) { + FileViewController.openWithExternalApplication(path); }; From 3cbacd8b51f1417c263640a553d3f3da2d35f77a Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Tue, 10 Mar 2020 11:53:57 +0530 Subject: [PATCH 121/149] detect graphics file in a project --- .../GraphicsFile.js | 83 +++++++++++++++++++ .../OpenWithExternalApplication/main.js | 8 +- .../node/OpenWithExternalApplicationDomain.js | 64 +++++++++++++- .../node/package.json | 3 +- 4 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/extensions/default/OpenWithExternalApplication/GraphicsFile.js diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js new file mode 100644 index 00000000000..e46f13e0df7 --- /dev/null +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2013 - present 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. + * + */ + +define(function (require, exports, module) { + "use strict"; + + + var PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + Strings = brackets.getModule("strings"), + ProjectManager = brackets.getModule("project/ProjectManager"); + + + var _requestID = 0, + _initialized = false; + + var _graphicsFileTypes = ["jpg", "jpeg", "png", "svg", "xd", "psd", "ai"]; + + var _nodeDomain; + + function init(nodeDomain) { + + if(_initialized) { + return; + } + _initialized = true; + + _nodeDomain = nodeDomain; + + _nodeDomain.on('checkFileTypesInFolderResponse', function (event, response) { + if(response.id !== _requestID) { + return; + } + _graphicsFilePresentInProject(response.present) + }); + + ProjectManager.on("projectOpen", function () { + _checkForGraphicsFileInPrjct(); + }); + + _checkForGraphicsFileInPrjct(); + + } + + + function _checkForGraphicsFileInPrjct() { + + _nodeDomain.exec("checkFileTypesInFolder", { + extensions: _graphicsFileTypes.join(), + folder: ProjectManager.getProjectRoot().fullPath, + reqId: ++_requestID + }); + + } + + function _graphicsFilePresentInProject(isPresent) { + + console.log("Graphics File present in project", isPresent); + + } + + exports.init = init; + +}); diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index 535a6333ee7..419946c7164 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -29,9 +29,11 @@ define(function (require, exports, module) { PreferencesManager = brackets.getModule("preferences/PreferencesManager"), Strings = brackets.getModule("strings"), FileViewController = brackets.getModule("project/FileViewController"), + ProjectManager = brackets.getModule("project/ProjectManager"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), NodeDomain = brackets.getModule("utils/NodeDomain"), - FileUtils = brackets.getModule("file/FileUtils"); + FileUtils = brackets.getModule("file/FileUtils"), + GraphicsFile = require("GraphicsFile"); /** * @private @@ -47,6 +49,8 @@ define(function (require, exports, module) { var extensionToExtApplicationMap = {}; + var graphicsFileTypes = [".jpg", ".jpeg", ".png", ".svg", ".xd", ".psd", ".ai"]; + function _openWithExternalApplication(event, path) { _nodeDomain.exec("open", { path: path, @@ -66,6 +70,8 @@ define(function (require, exports, module) { FileViewController.on("openWithExternalApplication", _openWithExternalApplication); AppInit.appReady(function () { + + GraphicsFile.init(_nodeDomain); extensionToExtApplicationMap = PreferencesManager.get("externalApplications"); FileUtils.addExtensionToExternalAppList(Object.keys(extensionToExtApplicationMap)); }); diff --git a/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js index 8a864689ef6..b0987614bb0 100644 --- a/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js +++ b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js @@ -25,7 +25,9 @@ /*jslint node: true */ "use strict"; -var open = require("open"); +var open = require("open"), + Glob = require("glob").Glob, + path = require('path'); var _domainManager; @@ -39,6 +41,39 @@ function _openWithExternalApplication(params) { open(params.path, application); } +/** + * @private + * + * @param {Object} params Object to use + */ +function _checkFileTypesInFolder(params) { + + var extList = params.extensions, + dirPath = path.normalize(params.folder), + + pattern = dirPath + "/**/*.+(" + extList.replace(",", "|") + ")"; + //pattern = dirPath + "/**/*.jpg"; + + var mg = new Glob(pattern, function (err, matches) { + + var respObj = { + id: params.reqId, + present: matches.length > 0 ? true : false + } + _domainManager.emitEvent('OpenWithExternalApplication', 'checkFileTypesInFolderResponse', [respObj]); + }); + + mg.on("match", function() { + //mg.abort(); + var respObj = { + id: params.reqId, + present: true + } + //_domainManager.emitEvent('OpenWithExternalApplication', 'checkFileTypesInFolderResponse', [respObj]); + }); + +} + /** * Initializes the OpenWithExternalEditor domain with its commands. @@ -50,6 +85,7 @@ function init(domainManager) { if (!domainManager.hasDomain("OpenWithExternalApplication")) { domainManager.registerDomain("OpenWithExternalApplication", {major: 0, minor: 1}); } + _domainManager.registerCommand( "OpenWithExternalApplication", "open", @@ -63,6 +99,32 @@ function init(domainManager) { }], [] ); + + _domainManager.registerCommand( + "OpenWithExternalApplication", + "checkFileTypesInFolder", + _checkFileTypesInFolder, + true, + "looks for File Types in a folder.", + [{ + name: "params", + type: "object", + description: "Params Object having File Extensions and Folder Path." + }], + [] + ); + + _domainManager.registerEvent( + "OpenWithExternalApplication", + "checkFileTypesInFolderResponse", + [ + { + name: "msgObj", + type: "object", + description: "json object containing message info to pass to brackets" + } + ] + ); } exports.init = init; diff --git a/src/extensions/default/OpenWithExternalApplication/node/package.json b/src/extensions/default/OpenWithExternalApplication/node/package.json index 65bb049736e..0e646495964 100644 --- a/src/extensions/default/OpenWithExternalApplication/node/package.json +++ b/src/extensions/default/OpenWithExternalApplication/node/package.json @@ -1,6 +1,7 @@ { "name": "brackets-open-external_application", "dependencies": { - "open": "0.0.5" + "open": "0.0.5", + "glob": "7.1.1" } } From b5d9ffdfa434236ffceb7f86a22364d5f62f40ff Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Tue, 10 Mar 2020 11:59:52 +0530 Subject: [PATCH 122/149] Binary file should not be opened if associated with external application --- src/language/languages.json | 2 +- src/project/FileTreeView.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/language/languages.json b/src/language/languages.json index 1de22f94d48..5c8e9ef0b93 100644 --- a/src/language/languages.json +++ b/src/language/languages.json @@ -291,7 +291,7 @@ "jsz", "lib", "mpeg", "mpg", "mp4", "msi", "node", "o", "obj", "odc", "odb", "odf", "odg", "odp", "ods", "odt", "otf", "pak", "pdb", "pdf", "pdi", "ppt", "pptx", "psd", "rar", "sdf", "so", "sqlite", "suo", "svgz", - "swf", "tar", "tif", "tiff", "ttf", "woff", "xls", "xlsx", "zip" + "swf", "tar", "tif", "tiff", "ttf", "woff", "xls", "xlsx", "zip", "xd" ], "isBinary": true }, diff --git a/src/project/FileTreeView.js b/src/project/FileTreeView.js index ec71c797664..d7ec88b98c9 100644 --- a/src/project/FileTreeView.js +++ b/src/project/FileTreeView.js @@ -555,10 +555,16 @@ define(function (require, exports, module) { }); } } else { - this.props.actions.setSelected(this.myPath(), - FileUtils.shouldOpenInExternalApplication( - FileUtils.getFileExtension(this.myPath()).toLowerCase() - )); + var language = LanguageManager.getLanguageForPath(this.myPath()), + doNotOpen = false; + if (language && language.isBinary() && "image" !== language.getId() && + FileUtils.shouldOpenInExternalApplication( + FileUtils.getFileExtension(this.myPath()).toLowerCase() + ) + ) { + doNotOpen = true; + } + this.props.actions.setSelected(this.myPath(), doNotOpen); } e.stopPropagation(); e.preventDefault(); From e8a829cbcf95853f57a09657f69f2f3c607310ac Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 12 Mar 2020 20:34:43 +0530 Subject: [PATCH 123/149] changes to detect graphics file in a project --- .../GraphicsFile.js | 54 ++++++++++++++++++- .../node/OpenWithExternalApplicationDomain.js | 11 ++-- src/nls/root/strings.js | 7 ++- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js index e46f13e0df7..55f79d83d00 100644 --- a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -27,13 +27,16 @@ define(function (require, exports, module) { var PreferencesManager = brackets.getModule("preferences/PreferencesManager"), Strings = brackets.getModule("strings"), - ProjectManager = brackets.getModule("project/ProjectManager"); + ProjectManager = brackets.getModule("project/ProjectManager"), + Dialogs = brackets.getModule("widgets/Dialogs"), + DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"); var _requestID = 0, _initialized = false; var _graphicsFileTypes = ["jpg", "jpeg", "png", "svg", "xd", "psd", "ai"]; + //var _graphicsFileTypes = [ "psd"]; var _nodeDomain; @@ -64,6 +67,10 @@ define(function (require, exports, module) { function _checkForGraphicsFileInPrjct() { + if(PreferencesManager.getViewState("AssociateGraphicsFileDialogShown")) { + return; + } + _nodeDomain.exec("checkFileTypesInFolder", { extensions: _graphicsFileTypes.join(), folder: ProjectManager.getProjectRoot().fullPath, @@ -74,7 +81,50 @@ define(function (require, exports, module) { function _graphicsFilePresentInProject(isPresent) { - console.log("Graphics File present in project", isPresent); + if(!isPresent) { + return; + } + + Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_INFO, + Strings.ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE, + Strings.ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG, + [ + { className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, + text: Strings.BUTTON_NO + }, + { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, + text: Strings.BUTTON_YES + } + ] + ).done(function (id) { + + if(id !== Dialogs.DIALOG_BTN_OK) + return; + + brackets.app.getSystemDefaultApp(_graphicsFileTypes.join(), function (err, out) { + var associateApp = out.split(','), + fileTypeToAppMap = {}; + + associateApp.forEach(function(item) { + fileTypeToAppMap[item.split(':')[0]] = item.split(':')[1] + }); + Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_INFO, + Strings.ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE, + out, + [ + { className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, + text: Strings.BUTTON_NO + }, + { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, + text: Strings.BUTTON_YES + } + ] + ) + }); + }); + PreferencesManager.setViewState("AssociateGraphicsFileDialogShown", true); } diff --git a/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js index b0987614bb0..996a99bdef1 100644 --- a/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js +++ b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js @@ -51,10 +51,9 @@ function _checkFileTypesInFolder(params) { var extList = params.extensions, dirPath = path.normalize(params.folder), - pattern = dirPath + "/**/*.+(" + extList.replace(",", "|") + ")"; - //pattern = dirPath + "/**/*.jpg"; + pattern = dirPath + "/**/*.+(" + extList.replace(/,/g, "|") + ")"; - var mg = new Glob(pattern, function (err, matches) { + var globMgr = new Glob(pattern, function (err, matches) { var respObj = { id: params.reqId, @@ -63,13 +62,13 @@ function _checkFileTypesInFolder(params) { _domainManager.emitEvent('OpenWithExternalApplication', 'checkFileTypesInFolderResponse', [respObj]); }); - mg.on("match", function() { - //mg.abort(); + globMgr.on("match", function() { + globMgr.abort(); var respObj = { id: params.reqId, present: true } - //_domainManager.emitEvent('OpenWithExternalApplication', 'checkFileTypesInFolderResponse', [respObj]); + _domainManager.emitEvent('OpenWithExternalApplication', 'checkFileTypesInFolderResponse', [respObj]); }); } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index e01b2300aeb..69f8803d620 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -906,5 +906,10 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID" : "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}.", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE" : "Add File type association to external App here" + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE" : "Add File type association to external App here", + + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE" : "Open Graphics Files in external editors?", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG" : "Click Yes to associate the graphic files with its appropriate external editors." + + }); From 52e9414d35aeee6797898c89f8d85e8e04fe5952 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 12 Mar 2020 20:40:04 +0530 Subject: [PATCH 124/149] corrected eslint error --- .../default/OpenWithExternalApplication/GraphicsFile.js | 9 +++++---- .../node/OpenWithExternalApplicationDomain.js | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js index 55f79d83d00..81c88ab5fbd 100644 --- a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -53,7 +53,7 @@ define(function (require, exports, module) { if(response.id !== _requestID) { return; } - _graphicsFilePresentInProject(response.present) + _graphicsFilePresentInProject(response.present); }); ProjectManager.on("projectOpen", function () { @@ -99,15 +99,16 @@ define(function (require, exports, module) { ] ).done(function (id) { - if(id !== Dialogs.DIALOG_BTN_OK) + if(id !== Dialogs.DIALOG_BTN_OK) { return; + } brackets.app.getSystemDefaultApp(_graphicsFileTypes.join(), function (err, out) { var associateApp = out.split(','), fileTypeToAppMap = {}; associateApp.forEach(function(item) { - fileTypeToAppMap[item.split(':')[0]] = item.split(':')[1] + fileTypeToAppMap[item.split(':')[0]] = item.split(':')[1]; }); Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_INFO, @@ -121,7 +122,7 @@ define(function (require, exports, module) { text: Strings.BUTTON_YES } ] - ) + ); }); }); PreferencesManager.setViewState("AssociateGraphicsFileDialogShown", true); diff --git a/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js index 996a99bdef1..e5a7e414585 100644 --- a/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js +++ b/src/extensions/default/OpenWithExternalApplication/node/OpenWithExternalApplicationDomain.js @@ -58,7 +58,7 @@ function _checkFileTypesInFolder(params) { var respObj = { id: params.reqId, present: matches.length > 0 ? true : false - } + }; _domainManager.emitEvent('OpenWithExternalApplication', 'checkFileTypesInFolderResponse', [respObj]); }); @@ -67,7 +67,7 @@ function _checkFileTypesInFolder(params) { var respObj = { id: params.reqId, present: true - } + }; _domainManager.emitEvent('OpenWithExternalApplication', 'checkFileTypesInFolderResponse', [respObj]); }); From 2dea43e2cb4a45054601fe4a9c3e25d55185da51 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 13 Mar 2020 00:57:38 +0530 Subject: [PATCH 125/149] updating preferences file with default app --- .../GraphicsFile.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js index 81c88ab5fbd..b295c0c8e01 100644 --- a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -36,7 +36,6 @@ define(function (require, exports, module) { _initialized = false; var _graphicsFileTypes = ["jpg", "jpeg", "png", "svg", "xd", "psd", "ai"]; - //var _graphicsFileTypes = [ "psd"]; var _nodeDomain; @@ -104,12 +103,29 @@ define(function (require, exports, module) { } brackets.app.getSystemDefaultApp(_graphicsFileTypes.join(), function (err, out) { + + if(err) { + return; + } var associateApp = out.split(','), fileTypeToAppMap = {}; associateApp.forEach(function(item) { - fileTypeToAppMap[item.split(':')[0]] = item.split(':')[1]; + fileTypeToAppMap[item.split('##')[0]] = item.split('##')[1]; }); + + var prefs = PreferencesManager.get('externalApplications'); + + for (var key in fileTypeToAppMap) { + if (fileTypeToAppMap.hasOwnProperty(key)) { + if(key && !prefs[key]) { + prefs[key] = "default"; + } + } + } + + PreferencesManager.set('externalApplications', prefs); + Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_INFO, Strings.ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE, From 7154baf13a1fd78b1f535781719aaaafc472434f Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 13 Mar 2020 01:05:51 +0530 Subject: [PATCH 126/149] code clean up --- src/extensions/default/OpenWithExternalApplication/main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index 419946c7164..a8e7ad69995 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -49,8 +49,6 @@ define(function (require, exports, module) { var extensionToExtApplicationMap = {}; - var graphicsFileTypes = [".jpg", ".jpeg", ".png", ".svg", ".xd", ".psd", ".ai"]; - function _openWithExternalApplication(event, path) { _nodeDomain.exec("open", { path: path, From 6f33003624e9e4d3fef98d4ee2d74af35b224fcf Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 13 Mar 2020 01:13:44 +0530 Subject: [PATCH 127/149] code clean up --- src/extensions/default/OpenWithExternalApplication/main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index a8e7ad69995..5587da39775 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -29,7 +29,6 @@ define(function (require, exports, module) { PreferencesManager = brackets.getModule("preferences/PreferencesManager"), Strings = brackets.getModule("strings"), FileViewController = brackets.getModule("project/FileViewController"), - ProjectManager = brackets.getModule("project/ProjectManager"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), NodeDomain = brackets.getModule("utils/NodeDomain"), FileUtils = brackets.getModule("file/FileUtils"), From 9c722a6be5a166d24b167364172440190baab09e Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 13 Mar 2020 12:42:55 +0530 Subject: [PATCH 128/149] Application Name correction --- .../default/OpenWithExternalApplication/GraphicsFile.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js index b295c0c8e01..ce30d3650b7 100644 --- a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -119,7 +119,11 @@ define(function (require, exports, module) { for (var key in fileTypeToAppMap) { if (fileTypeToAppMap.hasOwnProperty(key)) { if(key && !prefs[key]) { - prefs[key] = "default"; + prefs[key] = fileTypeToAppMap[key]; + if(brackets.platform === "win" && !fileTypeToAppMap[key].endsWith('.exe') && + !fileTypeToAppMap[key].endsWith('.EXE')) { + prefs[key] = "default"; + } } } } From 13bf4d2e8f36f36e7c559cf2edff1e723d3995cd Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 13 Mar 2020 16:45:43 +0530 Subject: [PATCH 129/149] addressed review comments --- src/file/FileUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/file/FileUtils.js b/src/file/FileUtils.js index 819168d12b4..284c839b44e 100644 --- a/src/file/FileUtils.js +++ b/src/file/FileUtils.js @@ -546,9 +546,9 @@ define(function (require, exports, module) { */ function addExtensionToExternalAppList(ext) { - if(typeof ext !== 'string') { + if(Array.isArray(ext)) { extListToBeOpenedInExtApp = ext; - } else { + } else if (typeof ext === 'string'){ extListToBeOpenedInExtApp.push(ext); } } From 3f1cfeb5bdc482a95df2bda05de3d720f7cea811 Mon Sep 17 00:00:00 2001 From: walf Date: Fri, 13 Mar 2020 09:55:28 -0700 Subject: [PATCH 130/149] Updated by ALF automation. --- src/nls/fr/strings.js | 5 ++++- src/nls/ja/strings.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index abf25a4580d..b2f3fd5322d 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -903,5 +903,8 @@ define({ "REMOTE_DEBUGGING_ENABLED": "Débogage à distance activé sur localhost:", // Remote debugging port argument is invalid - "REMOTE_DEBUGGING_PORT_INVALID": "Impossible d’activer le débogage à distance sur le port {0}. Les numéros de port doivent être compris entre {1} et {2}." + "REMOTE_DEBUGGING_PORT_INVALID": "Impossible d’activer le débogage à distance sur le port {0}. Les numéros de port doivent être compris entre {1} et {2}.", + + //Associate File Type to External App + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Add File type association to external App here" }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index c4453363e6e..2d9c27fe7ee 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -903,5 +903,8 @@ define({ "REMOTE_DEBUGGING_ENABLED": "次のローカルホストでリモートデバッグが有効になりました。localhost:", // Remote debugging port argument is invalid - "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。" + "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。", + + //Associate File Type to External App + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Add File type association to external App here" }); From 4d7268dc9a5d6e5cdc2754d42c647f5a4eed3122 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Tue, 17 Mar 2020 23:56:17 +0530 Subject: [PATCH 131/149] added analytics data loggiing --- .../GraphicsFile.js | 94 +++++++++++++++---- src/nls/root/strings.js | 7 +- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js index ce30d3650b7..894ed7e0b65 100644 --- a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -27,9 +27,11 @@ define(function (require, exports, module) { var PreferencesManager = brackets.getModule("preferences/PreferencesManager"), Strings = brackets.getModule("strings"), + StringsUtils = brackets.getModule("utils/StringUtils"), ProjectManager = brackets.getModule("project/ProjectManager"), Dialogs = brackets.getModule("widgets/Dialogs"), - DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"); + DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"), + HealthLogger = brackets.getModule("utils/HealthLogger"); var _requestID = 0, @@ -41,7 +43,7 @@ define(function (require, exports, module) { function init(nodeDomain) { - if(_initialized) { + if (_initialized) { return; } _initialized = true; @@ -49,7 +51,7 @@ define(function (require, exports, module) { _nodeDomain = nodeDomain; _nodeDomain.on('checkFileTypesInFolderResponse', function (event, response) { - if(response.id !== _requestID) { + if (response.id !== _requestID) { return; } _graphicsFilePresentInProject(response.present); @@ -66,7 +68,7 @@ define(function (require, exports, module) { function _checkForGraphicsFileInPrjct() { - if(PreferencesManager.getViewState("AssociateGraphicsFileDialogShown")) { + if (PreferencesManager.getViewState("AssociateGraphicsFileDialogShown")) { return; } @@ -80,7 +82,7 @@ define(function (require, exports, module) { function _graphicsFilePresentInProject(isPresent) { - if(!isPresent) { + if (!isPresent) { return; } @@ -90,28 +92,66 @@ define(function (require, exports, module) { Strings.ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG, [ { className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, - text: Strings.BUTTON_NO + text: Strings.CANCEL }, { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, - text: Strings.BUTTON_YES + text: Strings.OK } ] ).done(function (id) { - - if(id !== Dialogs.DIALOG_BTN_OK) { + + if (id !== Dialogs.DIALOG_BTN_OK) { + HealthLogger.sendAnalyticsData( + "externalEditorsCancelled", + "usage", + "externalEditors", + "Cancelled", + "" + ); return; } + HealthLogger.sendAnalyticsData( + "LinkExternalEditors", + "usage", + "externalEditors", + "LinkExternalEditors", + "" + ); brackets.app.getSystemDefaultApp(_graphicsFileTypes.join(), function (err, out) { - if(err) { + if (err) { return; } var associateApp = out.split(','), - fileTypeToAppMap = {}; + fileTypeToAppMap = {}, + AppToFileTypeMap = {}; + + associateApp.forEach(function (item) { + + var filetype = item.split('##')[0], + app = item.split('##')[1]; + + if (!filetype) { + return; + } + + if (filetype === "xd") { + if (app.toLowerCase() !== "adobe xd" && app !== "adobe.cc.xd") { + return; + } + app = "Adobe XD"; + } + fileTypeToAppMap[filetype] = app; - associateApp.forEach(function(item) { - fileTypeToAppMap[item.split('##')[0]] = item.split('##')[1]; + if (brackets.platform === "win" && !app.toLowerCase().endsWith('.exe')) { + app = app.substring(app.lastIndexOf('//') + 2, app.length - 4); + } + if (AppToFileTypeMap[app]) { + AppToFileTypeMap[app].push(filetype); + } else { + AppToFileTypeMap[app] = [filetype]; + } }); var prefs = PreferencesManager.get('externalApplications'); @@ -120,26 +160,40 @@ define(function (require, exports, module) { if (fileTypeToAppMap.hasOwnProperty(key)) { if(key && !prefs[key]) { prefs[key] = fileTypeToAppMap[key]; - if(brackets.platform === "win" && !fileTypeToAppMap[key].endsWith('.exe') && - !fileTypeToAppMap[key].endsWith('.EXE')) { + if(brackets.platform === "win" && !fileTypeToAppMap[key].toLowerCase().endsWith('.exe')) { prefs[key] = "default"; } + HealthLogger.sendAnalyticsData( + "AddExternalEditorForFileType_" + key.toUpperCase(), + "usage", + "externalEditors", + "AddExternalEditorForFileType_" + key.toUpperCase(), + "" + ); } } } PreferencesManager.set('externalApplications', prefs); + var str = ""; + for(var app in AppToFileTypeMap) { + str += AppToFileTypeMap[app].join() + "->" + app + "
    "; + } + + if(!str) { + return; + } + + str+="
    "; + Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_INFO, Strings.ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE, - out, + StringsUtils.format(Strings.ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG, str), [ - { className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, - text: Strings.BUTTON_NO - }, { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, - text: Strings.BUTTON_YES + text: Strings.OK } ] ); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 69f8803d620..5dbd01360ec 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -906,10 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID" : "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}.", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE" : "Add File type association to external App here", + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE" : "Associate File type to external App settings. e.g { \"\": \"\" } app_name is OS dependant, for example \"google chrome\" on macOS and \"chrome\" on Windows. app_name can also be given as \"default\" for OS default application.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE" : "Open Graphics Files in external editors?", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG" : "Click Yes to associate the graphic files with its appropriate external editors." + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE" : "Open Graphic Files in External Editors.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG" : "Your current folder has graphic file types which are not supported by Brackets.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG" : "Following file types have been successfully associated with default applications.
    {0} You can further add new file type associations or customize in brackets.json by going to “Debug->Open Preferences File” menu." }); From 5fb5763e6369d90a3974018810f16a38b2eafd33 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Wed, 18 Mar 2020 00:25:48 +0530 Subject: [PATCH 132/149] code corrections --- .../default/OpenWithExternalApplication/GraphicsFile.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js index 894ed7e0b65..d8ac00ce077 100644 --- a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -137,15 +137,16 @@ define(function (require, exports, module) { } if (filetype === "xd") { - if (app.toLowerCase() !== "adobe xd" && app !== "adobe.cc.xd") { + if (app.toLowerCase() !== "adobe xd" && app.toLowerCase() !== "adobe.cc.xd") { return; } + app = "Adobe XD"; } fileTypeToAppMap[filetype] = app; - if (brackets.platform === "win" && !app.toLowerCase().endsWith('.exe')) { - app = app.substring(app.lastIndexOf('//') + 2, app.length - 4); + if (brackets.platform === "win" && app.toLowerCase().endsWith('.exe')) { + app = app.substring(app.lastIndexOf('\\') + 1, app.length - 4); } if (AppToFileTypeMap[app]) { AppToFileTypeMap[app].push(filetype); From 950e72a3a627f5bc280ae1342a701b29605e6bff Mon Sep 17 00:00:00 2001 From: walf Date: Wed, 18 Mar 2020 09:56:39 -0700 Subject: [PATCH 133/149] Updated by ALF automation. --- src/nls/fr/strings.js | 8 +++++++- src/nls/ja/strings.js | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index b2f3fd5322d..861a35d3729 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -906,5 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID": "Impossible d’activer le débogage à distance sur le port {0}. Les numéros de port doivent être compris entre {1} et {2}.", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Add File type association to external App here" + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Associate File type to external App settings. e.g { \"\": \"\" } app_name is OS dependant, for example \"google chrome\" on macOS and \"chrome\" on Windows. app_name can also be given as \"default\" for OS default application.", + + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "Open Graphic Files in External Editors.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by Brackets.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You can further add new file type associations or customize in brackets.json by going to “Debug->Open Preferences File” menu." + + }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index 2d9c27fe7ee..a0ef5b44586 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -906,5 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Add File type association to external App here" + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Associate File type to external App settings. e.g { \"\": \"\" } app_name is OS dependant, for example \"google chrome\" on macOS and \"chrome\" on Windows. app_name can also be given as \"default\" for OS default application.", + + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "Open Graphic Files in External Editors.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by Brackets.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You can further add new file type associations or customize in brackets.json by going to “Debug->Open Preferences File” menu." + + }); From f9f584eeb5150a28dc69d5b58a627ee68596c20d Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 19 Mar 2020 12:10:12 +0530 Subject: [PATCH 134/149] On Wiindows Path is converted to windows style when opening with external editor --- .../default/OpenWithExternalApplication/main.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index 5587da39775..f9151b42d7d 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -48,9 +48,16 @@ define(function (require, exports, module) { var extensionToExtApplicationMap = {}; + function convertUnixPathToWindowsPath(path) { + if (brackets.platform === "win") { + path = path.split("/").join("\\"); + } + return path; + } + function _openWithExternalApplication(event, path) { _nodeDomain.exec("open", { - path: path, + path: convertUnixPathToWindowsPath(path), app: extensionToExtApplicationMap[FileUtils.getFileExtension(path).toLowerCase()] }); } From 9a040bfb805aad1f1c21e8a5a2e2ca5d381fb5ca Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 19 Mar 2020 19:02:23 +0530 Subject: [PATCH 135/149] Updated strings for Graphics File Association --- src/nls/root/strings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 5dbd01360ec..2c5e3e5b285 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -906,11 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID" : "Cannot enable remote debugging on port {0}. Port numbers should be between {1} and {2}.", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE" : "Associate File type to external App settings. e.g { \"\": \"\" } app_name is OS dependant, for example \"google chrome\" on macOS and \"chrome\" on Windows. app_name can also be given as \"default\" for OS default application.", + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE" : "Mappings for file extension to external applications. Syntax: \"\": \"\", Use \"default\" to open files in system default application for the file type.", "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE" : "Open Graphic Files in External Editors.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG" : "Your current folder has graphic file types which are not supported by Brackets.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG" : "Following file types have been successfully associated with default applications.
    {0} You can further add new file type associations or customize in brackets.json by going to “Debug->Open Preferences File” menu." + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG" : "Your current folder has graphic file types which are not supported by {APP_NAME}.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG" : "Following file types have been successfully associated with default applications.
    {0} You have the option to change your preference on whether you delete/add new file type associations in brackets.json by going to “Debug->Open Preference File” menu." }); From 313bb4e480ef1a55f1f3a54f4d90d5afc68cc845 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 19 Mar 2020 19:05:38 +0530 Subject: [PATCH 136/149] Updated strings for Graphics File Association --- src/nls/root/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 2c5e3e5b285..bcd120c6880 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -910,7 +910,7 @@ define({ "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE" : "Open Graphic Files in External Editors.", "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG" : "Your current folder has graphic file types which are not supported by {APP_NAME}.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG" : "Following file types have been successfully associated with default applications.
    {0} You have the option to change your preference on whether you delete/add new file type associations in brackets.json by going to “Debug->Open Preference File” menu." + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG" : "Following file types have been successfully associated with default applications.
    {0} You have the option to change your preference on whether you delete/add new file type associations in brackets.json by going to “Debug->Open Preferences File” menu." }); From 3120f48dcef34e288cb95d0ea273da0d4a3c5a88 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 19 Mar 2020 21:14:03 +0530 Subject: [PATCH 137/149] On Wiindows Path is converted to windows style when opening with external editor --- src/extensions/default/OpenWithExternalApplication/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index f9151b42d7d..71ddd0db166 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -49,7 +49,7 @@ define(function (require, exports, module) { var extensionToExtApplicationMap = {}; function convertUnixPathToWindowsPath(path) { - if (brackets.platform === "win") { + if (brackets.platform === "win" && path && path.charAt(0).isAlpha()) { path = path.split("/").join("\\"); } return path; From ee9ede2c9c710ebc9d9f8fd6292731fe373dfab4 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Thu, 19 Mar 2020 22:46:26 +0530 Subject: [PATCH 138/149] Addressed review comments --- src/extensions/default/OpenWithExternalApplication/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index 71ddd0db166..6ab78eeb1c0 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -49,8 +49,8 @@ define(function (require, exports, module) { var extensionToExtApplicationMap = {}; function convertUnixPathToWindowsPath(path) { - if (brackets.platform === "win" && path && path.charAt(0).isAlpha()) { - path = path.split("/").join("\\"); + if (brackets.platform === "win" && path && path[1] === ":" && path[2] === "/") { + path = path.replace(RegExp('/','g'), '\\'); } return path; } From b301acc8d02b0695d2e45303c3cbd64ec5308007 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 20 Mar 2020 01:26:27 +0530 Subject: [PATCH 139/149] upgrading lodash node module to 4.17.15 from 4.17.4 --- npm-shrinkwrap.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 3ac8cb72839..acfb3ca93ec 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1946,9 +1946,9 @@ "dev": true }, "lodash": { - "version": "4.17.4", - "from": "lodash@4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + "version": "4.17.15", + "from": "lodash@4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz" }, "longest": { "version": "1.0.1", diff --git a/package.json b/package.json index 805dff57463..84ce74f281c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "chokidar": "1.6.1", "decompress-zip": "0.3.0", "fs-extra": "2.0.0", - "lodash": "4.17.4", + "lodash": "4.17.15", "npm": "3.10.10", "opn": "4.0.2", "request": "2.79.0", From 9f44657dc0f22a0c7d69f0bc5edb91fa7001a36f Mon Sep 17 00:00:00 2001 From: walf Date: Thu, 19 Mar 2020 21:35:52 -0700 Subject: [PATCH 140/149] Updated by ALF automation. --- src/nls/fr/strings.js | 6 +++--- src/nls/ja/strings.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index 861a35d3729..fd4be9f1e95 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -906,11 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID": "Impossible d’activer le débogage à distance sur le port {0}. Les numéros de port doivent être compris entre {1} et {2}.", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Associate File type to external App settings. e.g { \"\": \"\" } app_name is OS dependant, for example \"google chrome\" on macOS and \"chrome\" on Windows. app_name can also be given as \"default\" for OS default application.", + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Mappings for file extension to external applications. Syntax: \"\": \"\", Use \"default\" to open files in system default application for the file type.", "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "Open Graphic Files in External Editors.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by Brackets.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You can further add new file type associations or customize in brackets.json by going to “Debug->Open Preferences File” menu." + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by {APP_NAME}.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You have the option to change your preference on whether you delete/add new file type associations in brackets.json by going to “Debug->Open Preferences File” menu." }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index a0ef5b44586..e92ef1786f4 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -906,11 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Associate File type to external App settings. e.g { \"\": \"\" } app_name is OS dependant, for example \"google chrome\" on macOS and \"chrome\" on Windows. app_name can also be given as \"default\" for OS default application.", + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Mappings for file extension to external applications. Syntax: \"\": \"\", Use \"default\" to open files in system default application for the file type.", "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "Open Graphic Files in External Editors.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by Brackets.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You can further add new file type associations or customize in brackets.json by going to “Debug->Open Preferences File” menu." + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by {APP_NAME}.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You have the option to change your preference on whether you delete/add new file type associations in brackets.json by going to “Debug->Open Preferences File” menu." }); From d50cb268232ed190ce6333340249f207fb86b373 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 20 Mar 2020 12:01:15 +0530 Subject: [PATCH 141/149] check iin config file --- src/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.json b/src/config.json index cc1c874f73f..d954955a7c2 100644 --- a/src/config.json +++ b/src/config.json @@ -47,7 +47,7 @@ "chokidar": "1.6.1", "decompress-zip": "0.3.0", "fs-extra": "2.0.0", - "lodash": "4.17.4", + "lodash": "4.17.15", "npm": "3.10.10", "opn": "4.0.2", "request": "2.79.0", From 0af714b8a2a1905508499c17e4ab80b901d1fbea Mon Sep 17 00:00:00 2001 From: walf Date: Fri, 20 Mar 2020 09:56:14 -0700 Subject: [PATCH 142/149] Updated by ALF automation. --- src/nls/fr/strings.js | 8 ++++---- src/nls/ja/strings.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nls/fr/strings.js b/src/nls/fr/strings.js index fd4be9f1e95..7b164ab1288 100644 --- a/src/nls/fr/strings.js +++ b/src/nls/fr/strings.js @@ -906,11 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID": "Impossible d’activer le débogage à distance sur le port {0}. Les numéros de port doivent être compris entre {1} et {2}.", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Mappings for file extension to external applications. Syntax: \"\": \"\", Use \"default\" to open files in system default application for the file type.", + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Mappages d’extension de fichier avec des applications externes. Syntaxe : \"\": \"\", Utiliser « default » pour ouvrir les fichiers dans l’application par défaut du système pour le type de fichier.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "Open Graphic Files in External Editors.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by {APP_NAME}.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You have the option to change your preference on whether you delete/add new file type associations in brackets.json by going to “Debug->Open Preferences File” menu." + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "Ouvrez les fichiers graphiques dans des éditeurs externes.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Votre dossier actuel comporte des types de fichier graphique non pris en charge par {APP_NAME}.
    Vous pouvez à présent associer des types de fichiers spécifiques avec des éditeurs externes. Une fois l’association établie, vous pouvez ouvrir des fichiers graphiques tels que .xd, .psd, .jpg, .png, .ai et .svg, dans leur application par défaut en double-cliquant sur ces fichiers dans l’arborescence de fichiers.

    Cliquez sur le bouton OK pour associer les types de fichier graphique avec leur application par défaut respective.", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Les types de fichiers suivants ont été associés à des applications par défaut.
    {0} Vous pouvez modifier votre préférence concernant l’éventuel(le) suppression/ajout d’associations de type de fichier dans brackets.json via le menu Déboguer->Ouvrir le fichier des préférences." }); diff --git a/src/nls/ja/strings.js b/src/nls/ja/strings.js index e92ef1786f4..7a128abfb35 100644 --- a/src/nls/ja/strings.js +++ b/src/nls/ja/strings.js @@ -906,11 +906,11 @@ define({ "REMOTE_DEBUGGING_PORT_INVALID": "ポート {0} でリモートデバッグを有効にできません。ポート番号は、{1} から {2} の間で指定してください。", //Associate File Type to External App - "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "Mappings for file extension to external applications. Syntax: \"\": \"\", Use \"default\" to open files in system default application for the file type.", + "DESCRIPTION_EXTERNAL_APPLICATION_ASSOCIATE": "ファイル拡張子の外部アプリケーションへのマッピング。構文: \"\": \"\"。「default」を指定すると、そのファイルタイプに対してシステムでデフォルトに設定されているアプリケーションを使用してファイルが開きます。", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "Open Graphic Files in External Editors.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "Your current folder has graphic file types which are not supported by {APP_NAME}.
    You can now associate specific file types with external editors. Once associated, you can open graphic files like .xd, .psd, .jpg, .png, .ai, .svg in their default applications by double clicking on the files in File Tree.

    Please click on ‘Ok’ button to associate the graphic file types with their respective default applications.", - "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "Following file types have been successfully associated with default applications.
    {0} You have the option to change your preference on whether you delete/add new file type associations in brackets.json by going to “Debug->Open Preferences File” menu." + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_TITLE": "外部エディターでグラフィックファイルを開きます。", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_MSG": "現在のフォルダーには、{APP_NAME}でサポートされていないタイプのグラフィックファイルがあります。
    ここで、特定のファイルタイプを外部エディターに関連付けることができます。関連付けが完了すると、xd、.psd、.jpg、.png、.ai、.svgなどのグラフィックファイルをファイルツリーでダブルクリックすることで、デフォルトのアプリケーションで開くことができます。

    「OK」ボタンをクリックして、グラフィックファイルタイプをそれぞれのデフォルトアプリケーションに関連付けてください。", + "ASSOCIATE_GRAPHICS_FILE_TO_DEFAULT_APP_CNF_MSG": "次のファイルタイプが、デフォルトのアプリケーションに関連付けられました。
    {0} この設定は、brackets.json でファイルタイプの関連付けを削除し、新しい関連付けを追加するか、デバッグ/環境設定ファイルを開くメニューにアクセスして変更できます。" }); From 59e8e91518cb4ad33494bfbeb0ef9b5dcebcf630 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Mon, 23 Mar 2020 16:36:27 +0530 Subject: [PATCH 143/149] Netwrk path also need to be converted to windows style --- src/extensions/default/OpenWithExternalApplication/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index 6ab78eeb1c0..a1fc499c8de 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -49,7 +49,8 @@ define(function (require, exports, module) { var extensionToExtApplicationMap = {}; function convertUnixPathToWindowsPath(path) { - if (brackets.platform === "win" && path && path[1] === ":" && path[2] === "/") { + if (brackets.platform === "win" + && path && ((path[1] === ":" && path[2] === "/") || (path[0] === "/" && path[1] === "/"))) { path = path.replace(RegExp('/','g'), '\\'); } return path; From 2356f32a34d8a825ca1f41b1b4cb93cf94b92ecb Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Mon, 23 Mar 2020 16:43:52 +0530 Subject: [PATCH 144/149] Revert "check iin config file" This reverts commit d50cb268232ed190ce6333340249f207fb86b373. --- src/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.json b/src/config.json index d954955a7c2..cc1c874f73f 100644 --- a/src/config.json +++ b/src/config.json @@ -47,7 +47,7 @@ "chokidar": "1.6.1", "decompress-zip": "0.3.0", "fs-extra": "2.0.0", - "lodash": "4.17.15", + "lodash": "4.17.4", "npm": "3.10.10", "opn": "4.0.2", "request": "2.79.0", From 90cf9b09457a66237ade823b718a23f8ebe6f31b Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Mon, 23 Mar 2020 19:53:10 +0530 Subject: [PATCH 145/149] Revert "Revert "check iin config file"" This reverts commit 2356f32a34d8a825ca1f41b1b4cb93cf94b92ecb. --- src/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.json b/src/config.json index cc1c874f73f..d954955a7c2 100644 --- a/src/config.json +++ b/src/config.json @@ -47,7 +47,7 @@ "chokidar": "1.6.1", "decompress-zip": "0.3.0", "fs-extra": "2.0.0", - "lodash": "4.17.4", + "lodash": "4.17.15", "npm": "3.10.10", "opn": "4.0.2", "request": "2.79.0", From 35917cb272d681230d778f03bfb96d7b56c19c6c Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Tue, 24 Mar 2020 11:52:02 +0530 Subject: [PATCH 146/149] Addressed review comments --- src/extensions/default/OpenWithExternalApplication/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/OpenWithExternalApplication/main.js b/src/extensions/default/OpenWithExternalApplication/main.js index a1fc499c8de..3589f994c49 100644 --- a/src/extensions/default/OpenWithExternalApplication/main.js +++ b/src/extensions/default/OpenWithExternalApplication/main.js @@ -32,6 +32,7 @@ define(function (require, exports, module) { ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), NodeDomain = brackets.getModule("utils/NodeDomain"), FileUtils = brackets.getModule("file/FileUtils"), + FileSystem = brackets.getModule("filesystem/FileSystem"), GraphicsFile = require("GraphicsFile"); /** @@ -49,8 +50,7 @@ define(function (require, exports, module) { var extensionToExtApplicationMap = {}; function convertUnixPathToWindowsPath(path) { - if (brackets.platform === "win" - && path && ((path[1] === ":" && path[2] === "/") || (path[0] === "/" && path[1] === "/"))) { + if (brackets.platform === "win" && path && FileSystem.isAbsolutePath(path)) { path = path.replace(RegExp('/','g'), '\\'); } return path; From d99dbdd6bce812f9c71d9693b0a1142ba2de41b0 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar Date: Fri, 27 Mar 2020 12:55:09 +0530 Subject: [PATCH 147/149] Graphics File Assciate should get triggered after UUID is generated --- .../default/OpenWithExternalApplication/GraphicsFile.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js index d8ac00ce077..1ffc72c1173 100644 --- a/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js +++ b/src/extensions/default/OpenWithExternalApplication/GraphicsFile.js @@ -72,6 +72,13 @@ define(function (require, exports, module) { return; } + var userUuid = PreferencesManager.getViewState("UUID"), + olderUuid = PreferencesManager.getViewState("OlderUUID"); + + if(!(userUuid || olderUuid)) { + return; + } + _nodeDomain.exec("checkFileTypesInFolder", { extensions: _graphicsFileTypes.join(), folder: ProjectManager.getProjectRoot().fullPath, From f0cae55ce80c380a5c5c665110623f21cce966b3 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 1 Mar 2021 16:40:07 +0530 Subject: [PATCH 148/149] EOL Notification (#15325) * Update README.md * Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 481d454ef3f..1fccbba1cf9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ + +| :warning: On September 1, 2021, Adobe will end support for Brackets. If you would like to continue using, maintaining, and improving Brackets, you may fork the project on [GitHub](https://github.com/adobe/brackets). Through Adobe’s partnership with Microsoft, we encourage users to migrate to [Visual Studio Code](https://code.visualstudio.com/), Microsoft’s free code editor built on open source. +| --- + + Welcome to Brackets! [![Build Status](https://travis-ci.org/adobe/brackets.svg?branch=master)](https://travis-ci.org/adobe/brackets) ------------------- From d55ac606ac44e7169dbeaf959c9ed47dac6a6b9a Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 18 Mar 2021 19:22:19 +0530 Subject: [PATCH 149/149] Change hyperlink --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1fccbba1cf9..a71783965e9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -| :warning: On September 1, 2021, Adobe will end support for Brackets. If you would like to continue using, maintaining, and improving Brackets, you may fork the project on [GitHub](https://github.com/adobe/brackets). Through Adobe’s partnership with Microsoft, we encourage users to migrate to [Visual Studio Code](https://code.visualstudio.com/), Microsoft’s free code editor built on open source. +| :warning: On September 1, 2021, Adobe will end support for Brackets. If you would like to continue using, maintaining, and improving Brackets, you may fork the project on [GitHub](https://github.com/adobe/brackets). Through Adobe’s partnership with Microsoft, we encourage users to migrate to [Visual Studio Code](https://aka.ms/brackets-to-vscode), Microsoft’s free code editor built on open source. | ---