Skip to content

Commit 70fba00

Browse files
committed
feat: load or unload current project as extension, extn development workflow
1 parent 50f9c22 commit 70fba00

7 files changed

Lines changed: 169 additions & 3 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This document outlines how to write your own extensions and themes for Phoenix Code.

src/command/Menus.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ define(function (require, exports, module) {
525525
*
526526
* @param {!string | Command} command - the command the menu will execute.
527527
* Pass Menus.DIVIDER for a menu divider, or just call addMenuDivider() instead.
528-
* @param {?string | Array.<{key: string, platform: string}>} keyBindings - register one
528+
* @param {?string | Array.<{key: string, platform: string}>} [keyBindings] - register one
529529
* one or more key bindings to associate with the supplied command.
530530
* @param {?string} [position] - constant defining the position of new MenuItem relative to
531531
* other MenuItems. Values:

src/document/DocumentCommandHandlers.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
// jshint ignore: start
2323
/*jslint regexp: true */
24+
/*globals logger*/
2425

2526
define(function (require, exports, module) {
2627

@@ -1777,22 +1778,42 @@ define(function (require, exports, module) {
17771778
* Restarts brackets Handler
17781779
* @param {boolean=} loadWithoutExtensions - true to restart without extensions,
17791780
* otherwise extensions are loadeed as it is durning a typical boot
1781+
* @param {Array<String>|string} loadDevExtensionPath If specified, will load the extension from the path. IF
1782+
* and empty array is specified, it will unload all dev extensions on reload.
17801783
*/
1781-
function handleReload(loadWithoutExtensions) {
1784+
function handleReload(loadWithoutExtensions, loadDevExtensionPath) {
17821785
var href = window.location.href,
17831786
params = new UrlParams();
17841787

17851788
// Make sure the Reload Without User Extensions parameter is removed
17861789
params.parse();
17871790

1791+
function _removeLoadDevExtensionPathParam() {
1792+
if (params.get("loadDevExtensionPath")) {
1793+
params.remove("loadDevExtensionPath");
1794+
// only remove logging flag if the flag is set for loadDevExtensionPath
1795+
if (params.get(logger.loggingOptions.LOCAL_STORAGE_KEYS.LOG_TO_CONSOLE_KEY)) {
1796+
params.remove(logger.loggingOptions.LOCAL_STORAGE_KEYS.LOG_TO_CONSOLE_KEY);
1797+
}
1798+
}
1799+
}
1800+
17881801
if (loadWithoutExtensions) {
17891802
if (!params.get("reloadWithoutUserExts")) {
17901803
params.put("reloadWithoutUserExts", true);
17911804
}
1805+
_removeLoadDevExtensionPathParam();
17921806
} else {
17931807
if (params.get("reloadWithoutUserExts")) {
17941808
params.remove("reloadWithoutUserExts");
17951809
}
1810+
if(loadDevExtensionPath && loadDevExtensionPath.length){
1811+
params.put("loadDevExtensionPath", loadDevExtensionPath);
1812+
// since we are loading a development extension, we have to enable detailed logs too on reload
1813+
params.put(logger.loggingOptions.LOCAL_STORAGE_KEYS.LOG_TO_CONSOLE_KEY, "true");
1814+
} else if (loadDevExtensionPath && loadDevExtensionPath.length === 0) {
1815+
_removeLoadDevExtensionPathParam();
1816+
}
17961817
}
17971818

17981819
if (href.indexOf("?") !== -1) {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
* Original work Copyright (c) 2012 - 2021 Adobe Systems Incorporated. All rights reserved.
6+
*
7+
* This program is free software: you can redistribute it and/or modify it
8+
* under the terms of the GNU Affero General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
15+
* for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
19+
*
20+
*/
21+
22+
/*globals path, logger*/
23+
24+
define(function (require, exports, module) {
25+
const ProjectManager = brackets.getModule("project/ProjectManager"),
26+
Commands = brackets.getModule("command/Commands"),
27+
CommandManager = brackets.getModule("command/CommandManager"),
28+
Strings = brackets.getModule("strings"),
29+
StringUtils = brackets.getModule("utils/StringUtils"),
30+
DocumentManager = brackets.getModule("document/DocumentManager"),
31+
DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"),
32+
Dialogs = brackets.getModule("widgets/Dialogs"),
33+
UrlParams = brackets.getModule("utils/UrlParams").UrlParams,
34+
FileSystem = brackets.getModule("filesystem/FileSystem");
35+
36+
function _showError(message, title = Strings.ERROR_LOADING_EXTENSION) {
37+
Dialogs.showModalDialog(
38+
DefaultDialogs.DIALOG_ID_ERROR,
39+
title, message
40+
);
41+
}
42+
43+
function _validatePackageJson(docText) {
44+
try {
45+
let packageJson = JSON.parse(docText);
46+
let requiredFields = ["name", "title", "description", "homepage", "version", "author", "license",
47+
"engines"];
48+
let missingFields = [];
49+
for(let requiredField of requiredFields){
50+
if(!packageJson[requiredField]){
51+
missingFields.push(requiredField);
52+
}
53+
}
54+
if(missingFields.length){
55+
_showError(StringUtils.format(Strings.ERROR_INVALID_EXTENSION_PACKAGE_FIELDS, missingFields));
56+
return false;
57+
}
58+
return true;
59+
} catch (e) {
60+
console.log("Cannot load extension", Strings.ERROR_INVALID_EXTENSION_PACKAGE);
61+
_showError(Strings.ERROR_INVALID_EXTENSION_PACKAGE);
62+
return false;
63+
}
64+
}
65+
66+
function loadCurrentExtension() {
67+
const projectRoot = ProjectManager.getProjectRoot().fullPath;
68+
const file = FileSystem.getFileForPath(projectRoot + "package.json");
69+
DocumentManager.getDocumentText(file).done(function (docText) {
70+
console.log(docText);
71+
if(!_validatePackageJson(docText)){
72+
return;
73+
}
74+
CommandManager.execute(Commands.APP_RELOAD, false, projectRoot);
75+
}).fail((err)=>{
76+
console.log("No extension package.json in ", file.fullPath, err);
77+
Dialogs.showModalDialog(
78+
DefaultDialogs.DIALOG_ID_ERROR,
79+
Strings.ERROR_LOADING_EXTENSION,
80+
Strings.ERROR_NO_EXTENSION_PACKAGE
81+
);
82+
});
83+
}
84+
85+
function unloadCurrentExtension() {
86+
CommandManager.execute(Commands.APP_RELOAD, false, []);
87+
}
88+
89+
function isProjectLoadedAsExtension() {
90+
const params = new UrlParams();
91+
92+
// Make sure the Reload Without User Extensions parameter is removed
93+
params.parse();
94+
return !!params.get("loadDevExtensionPath");
95+
}
96+
97+
exports.loadCurrentExtension = loadCurrentExtension;
98+
exports.unloadCurrentExtension = unloadCurrentExtension;
99+
exports.isProjectLoadedAsExtension = isProjectLoadedAsExtension;
100+
});

src/extensions/default/DebugCommands/main.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ define(function (require, exports, module) {
4444
Mustache = brackets.getModule("thirdparty/mustache/mustache"),
4545
Locales = brackets.getModule("nls/strings"),
4646
ProjectManager = brackets.getModule("project/ProjectManager"),
47+
extensionDevelopment = require("extensionDevelopment"),
4748
PerfDialogTemplate = require("text!htmlContent/perf-dialog.html"),
4849
LanguageDialogTemplate = require("text!htmlContent/language-dialog.html");
4950

@@ -66,7 +67,9 @@ define(function (require, exports, module) {
6667
* Debug commands IDs
6768
* @enum {string}
6869
*/
69-
const DEBUG_REFRESH_WINDOW = "debug.refreshWindow", // string must MATCH string in native code (brackets_extensions)
70+
const DEBUG_REFRESH_WINDOW = "debug.refreshWindow", // string must MATCH string in native code (brackets_extensions)
71+
DEBUG_LOAD_CURRENT_EXTENSION = "debug.loadCurrentExtension",
72+
DEBUG_UNLOAD_CURRENT_EXTENSION = "debug.unloadCurrentExtension",
7073
DEBUG_RUN_UNIT_TESTS = "debug.runUnitTests",
7174
DEBUG_SHOW_PERF_DATA = "debug.showPerfData",
7275
DEBUG_RELOAD_WITHOUT_USER_EXTS = "debug.reloadWithoutUserExts",
@@ -725,6 +728,12 @@ define(function (require, exports, module) {
725728
}
726729

727730
/* Register all the command handlers */
731+
let loadOrReloadString = extensionDevelopment.isProjectLoadedAsExtension() ?
732+
Strings.CMD_RELOAD_CURRENT_EXTENSION : Strings.CMD_LOAD_CURRENT_EXTENSION;
733+
CommandManager.register(loadOrReloadString, DEBUG_LOAD_CURRENT_EXTENSION,
734+
extensionDevelopment.loadCurrentExtension);
735+
CommandManager.register(Strings.CMD_UNLOAD_CURRENT_EXTENSION, DEBUG_UNLOAD_CURRENT_EXTENSION,
736+
extensionDevelopment.unloadCurrentExtension);
728737
CommandManager.register(Strings.CMD_REFRESH_WINDOW, DEBUG_REFRESH_WINDOW, handleReload);
729738
CommandManager.register(Strings.CMD_RELOAD_WITHOUT_USER_EXTS, DEBUG_RELOAD_WITHOUT_USER_EXTS, handleReloadWithoutUserExts);
730739
CommandManager.register(Strings.CMD_NEW_BRACKETS_WINDOW, DEBUG_NEW_BRACKETS_WINDOW, handleNewBracketsWindow);
@@ -751,6 +760,10 @@ define(function (require, exports, module) {
751760
var menu = Menus.addMenu(Strings.DEBUG_MENU, DEBUG_MENU, Menus.BEFORE, Menus.AppMenuBar.HELP_MENU);
752761
menu.addMenuItem(DEBUG_REFRESH_WINDOW, KeyboardPrefs.refreshWindow);
753762
menu.addMenuItem(DEBUG_RELOAD_WITHOUT_USER_EXTS, KeyboardPrefs.reloadWithoutUserExts);
763+
menu.addMenuItem(DEBUG_LOAD_CURRENT_EXTENSION);
764+
menu.addMenuItem(DEBUG_UNLOAD_CURRENT_EXTENSION, undefined, undefined, undefined, {
765+
hideWhenCommandDisabled: true
766+
});
754767
menu.addMenuItem(DEBUG_NEW_BRACKETS_WINDOW);
755768
menu.addMenuDivider();
756769
menu.addMenuItem(DEBUG_SWITCH_LANGUAGE);
@@ -767,6 +780,8 @@ define(function (require, exports, module) {
767780
menu.addMenuItem(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW); // this command will enable defaultPreferences and brackets preferences to be open side by side in split view.
768781
menu.addMenuItem(Commands.FILE_OPEN_KEYMAP); // this command is defined in core, but exposed only in Debug menu for now
769782

783+
CommandManager.get(DEBUG_UNLOAD_CURRENT_EXTENSION)
784+
.setEnabled(extensionDevelopment.isProjectLoadedAsExtension());
770785
_updateLogToConsoleMenuItemChecked();
771786
// exposed for convenience, but not official API
772787
exports._runUnitTests = _runUnitTests;

src/nls/root/strings.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,9 @@ define({
671671
"ERRORS": "Errors",
672672
"CMD_SHOW_DEV_TOOLS": "Show Developer Tools",
673673
"CMD_REFRESH_WINDOW": "Reload With Extensions",
674+
"CMD_LOAD_CURRENT_EXTENSION": "Load Project As Extension",
675+
"CMD_RELOAD_CURRENT_EXTENSION": "Reload Project As Extension",
676+
"CMD_UNLOAD_CURRENT_EXTENSION": "Unload Project As Extension",
674677
"CMD_RELOAD_WITHOUT_USER_EXTS": "Reload Without Extensions",
675678
"CMD_NEW_BRACKETS_WINDOW": "New {APP_NAME} Window",
676679
"CMD_LAUNCH_SCRIPT_MAC": "Install Command Line Shortcut",
@@ -689,6 +692,10 @@ define({
689692
"ERROR_CLTOOLS_SERVFAILED": "Internal error.",
690693
"ERROR_CLTOOLS_NOTSUPPORTED": "Command line shortcut is not supported on this OS.",
691694
"LAUNCH_SCRIPT_CREATE_SUCCESS": "Success! Now you can easily launch {APP_NAME} from the command line: <code>brackets myFile.txt</code> to open a file or <code>brackets myFolder</code> to switch projects. <br/><br/><a href='https://github.com/adobe/brackets/wiki/Command-Line-Arguments'>Learn more</a> about using {APP_NAME} from the command line.",
695+
"ERROR_LOADING_EXTENSION": "Error Loading Extension",
696+
"ERROR_NO_EXTENSION_PACKAGE": "<code>package.json</code> file not detected in current project. Is the current project a valid {APP_NAME} Extension?",
697+
"ERROR_INVALID_EXTENSION_PACKAGE": "<code>package.json</code> file is invalid JSON.",
698+
"ERROR_INVALID_EXTENSION_PACKAGE_FIELDS": "Required fields missing in <code>package.json</code>: [{0}]",
692699

693700
"LANGUAGE_TITLE": "Switch Language",
694701
"LANGUAGE_MESSAGE": "Language:",

src/utils/ExtensionLoader.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,20 @@ define(function (require, exports, module) {
493493
return _loadAll(directory, {baseUrl: directory}, "main", loadExtension);
494494
}
495495

496+
/**
497+
* Loads a given extension at the path from native directory.
498+
* @param directory
499+
* @return {!Promise}
500+
*/
501+
function loadExtensionFromNativeDirectory(directory) {
502+
logger.leaveTrail("loading custom extension from path: " + directory);
503+
const extConfig = {
504+
baseUrl: window.fsServerUrl.slice(0, -1) + directory
505+
};
506+
return loadExtension("ext" + directory.replace("/", "-"), // /fs/user/extpath to ext-fs-user-extpath
507+
extConfig, 'main');
508+
}
509+
496510
/**
497511
* Runs unit test for the extension that lives at baseUrl into its own Require.js context
498512
*
@@ -624,6 +638,12 @@ define(function (require, exports, module) {
624638
} else {
625639
paths = [];
626640
}
641+
if(params.get("loadDevExtensionPath")){
642+
let customLoadPaths = params.get("loadDevExtensionPath").split(",");
643+
for(let customPath of customLoadPaths){
644+
paths.push("custom:" + customPath);
645+
}
646+
}
627647
}
628648

629649
// Load extensions before restoring the project
@@ -646,6 +666,8 @@ define(function (require, exports, module) {
646666
var promise = Async.doInParallel(paths, function (extPath) {
647667
if(extPath === "default"){
648668
return loadAllDefaultExtensions();
669+
} else if(extPath.startsWith("custom:")){
670+
return loadExtensionFromNativeDirectory(extPath.replace("custom:", ""));
649671
} else {
650672
return loadAllExtensionsInNativeDirectory(extPath);
651673
}

0 commit comments

Comments
 (0)