From 6c34d70b45b4172095301c8227335c11b511998b Mon Sep 17 00:00:00 2001 From: Jake Stoeffler Date: Tue, 18 Jun 2013 02:06:32 -0400 Subject: [PATCH 01/26] Initial commit of language select dialog --- src/document/Document.js | 17 +++-- src/editor/EditorStatusBar.js | 70 ++++++++++++++++++--- src/htmlContent/switch-language-dialog.html | 20 ++++++ src/language/LanguageManager.js | 10 +++ src/nls/root/strings.js | 3 + src/styles/brackets.less | 4 ++ 6 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 src/htmlContent/switch-language-dialog.html diff --git a/src/document/Document.js b/src/document/Document.js index 810523c7de7..98aa917bbe4 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -423,18 +423,25 @@ define(function (require, exports, module) { Document.prototype.getLanguage = function () { return this.language; }; - + /** - * Updates the language according to the file extension + * Sets the language of this document to the given language. + * @param {!Language} language The language to be set for this document */ - Document.prototype._updateLanguage = function () { + Document.prototype.setLanguage = function (language) { var oldLanguage = this.language; - this.language = LanguageManager.getLanguageForPath(this.file.fullPath); - + this.language = language; if (oldLanguage && oldLanguage !== this.language) { $(this).triggerHandler("languageChanged", [oldLanguage, this.language]); } }; + + /** + * Updates the language according to the file extension + */ + Document.prototype._updateLanguage = function () { + this.setLanguage(LanguageManager.getLanguageForPath(this.file.fullPath)); + }; /** Called when Document.file has been modified (due to a rename) */ Document.prototype._notifyFilePathChanged = function () { diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 176086963a6..4962a9da366 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, window */ +/*global define, $, window, Mustache */ /** * Manages parts of the status bar related to the current editor's state. @@ -32,13 +32,18 @@ define(function (require, exports, module) { "use strict"; // Load dependent modules - var AppInit = require("utils/AppInit"), - EditorManager = require("editor/EditorManager"), - Editor = require("editor/Editor").Editor, - KeyEvent = require("utils/KeyEvent"), - StatusBar = require("widgets/StatusBar"), - Strings = require("strings"), - StringUtils = require("utils/StringUtils"); + var AppInit = require("utils/AppInit"), + CollectionUtils = require("utils/CollectionUtils"), + DefaultDialogs = require("widgets/DefaultDialogs"), + Dialogs = require("widgets/Dialogs"), + EditorManager = require("editor/EditorManager"), + Editor = require("editor/Editor").Editor, + KeyEvent = require("utils/KeyEvent"), + LanguageManager = require("language/LanguageManager"), + StatusBar = require("widgets/StatusBar"), + Strings = require("strings"), + StringUtils = require("utils/StringUtils"), + SwitchLanguageDialogTemplate = require("text!htmlContent/switch-language-dialog.html"); /* StatusBar indicators */ var $languageInfo, @@ -147,6 +152,48 @@ define(function (require, exports, module) { } } + /** + * Open a dialog allowing user to switch the language mode for the current + * document. + * Currently this is only triggered when the language name in the status + * bar is clicked, but it could easily become a menu option in the future. + * @param {!Editor} editor The editor for which to switch the language + */ + function _handleSwitchLanguage(editor) { + var languages = [], + selectedLanguage = editor.document.getLanguage().getId(), + template; + // populate list of languages + CollectionUtils.forEach(LanguageManager.getLanguages(), + function (lang) { + languages.push({ + label: lang.getName(), + language: lang.getId() + }); + }); + // sort list alphabetically (ignoring case) + languages = languages.sort(function (a, b) { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + }); + // render switch-language-dialog.html using the languages list + template = Mustache.render(SwitchLanguageDialogTemplate, // TODO: add file name to dialog title + $.extend({languages: languages}, Strings)); + // show the dialog and set the handler for OK button + Dialogs.showModalDialogUsingTemplate(template) + .done(function (btnId) { + if (btnId === Dialogs.DIALOG_BTN_OK) { + editor.document.setLanguage( + LanguageManager.getLanguage(selectedLanguage) + ); // TODO: persist the language setting? + } + }); + // set initial value and change handler for select box + $(".switch-language-dialog select").val(selectedLanguage) + .on("change", function () { + selectedLanguage = $(this).val(); + }); + } + function _init() { $languageInfo = $("#status-language"); $cursorInfo = $("#status-cursor"); @@ -180,7 +227,12 @@ define(function (require, exports, module) { }); $indentWidthInput.focus(function () { $indentWidthInput.select(); }); - + + // when language name clicked, open switch language dialog + $languageInfo.on("click", function () { + _handleSwitchLanguage(EditorManager.getActiveEditor()); + }); + _onActiveEditorChange(null, EditorManager.getActiveEditor(), null); } diff --git a/src/htmlContent/switch-language-dialog.html b/src/htmlContent/switch-language-dialog.html new file mode 100644 index 00000000000..acaf81141de --- /dev/null +++ b/src/htmlContent/switch-language-dialog.html @@ -0,0 +1,20 @@ + diff --git a/src/language/LanguageManager.js b/src/language/LanguageManager.js index dc9c7605134..1eedeae608b 100644 --- a/src/language/LanguageManager.js +++ b/src/language/LanguageManager.js @@ -238,6 +238,15 @@ define(function (require, exports, module) { return language || _fallbackLanguage; } + /** + * Returns all of the languages currently defined in the LanguageManager. + * @return {Object.} A map containing all of the + * languages currently defined. + */ + function getLanguages() { + return _languages; + } + /** * Resolves a CodeMirror mode to a Language object. * @param {!string} mode CodeMirror mode @@ -754,4 +763,5 @@ define(function (require, exports, module) { exports.defineLanguage = defineLanguage; exports.getLanguage = getLanguage; exports.getLanguageForPath = getLanguageForPath; + exports.getLanguages = getLanguages; }); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 2098b2427ba..558f20091dc 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -393,6 +393,9 @@ define({ "LANGUAGE_CANCEL" : "Cancel", "LANGUAGE_SYSTEM_DEFAULT" : "System Default", + "SWITCH_LANGUAGE_TITLE" : "Switch Language", + "SWITCH_LANGUAGE_MESSAGE" : "Language:", + /** * Locales */ diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 9e4f4de0378..f72b754819b 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -165,6 +165,10 @@ a, img { #status-language { border-right: 1px solid rgba(0, 0, 0, 0.1); + cursor: pointer; + &:hover { + text-decoration: underline; + } } } From 6e8f88189bb3135c3ac69d20557969886c102e33 Mon Sep 17 00:00:00 2001 From: Jake Stoeffler Date: Tue, 18 Jun 2013 22:22:59 -0400 Subject: [PATCH 02/26] Add reset button; add file name to dialog title --- src/editor/EditorStatusBar.js | 27 +++++++++++++-------- src/htmlContent/switch-language-dialog.html | 5 ++-- src/nls/root/strings.js | 4 ++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 4962a9da366..157c6f99022 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -157,11 +157,11 @@ define(function (require, exports, module) { * document. * Currently this is only triggered when the language name in the status * bar is clicked, but it could easily become a menu option in the future. - * @param {!Editor} editor The editor for which to switch the language + * @param {!Document} document The document for which to switch the language */ - function _handleSwitchLanguage(editor) { + function _handleSwitchLanguage(document) { var languages = [], - selectedLanguage = editor.document.getLanguage().getId(), + selectedLanguage = document.getLanguage().getId(), template; // populate list of languages CollectionUtils.forEach(LanguageManager.getLanguages(), @@ -175,16 +175,23 @@ define(function (require, exports, module) { languages = languages.sort(function (a, b) { return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); }); - // render switch-language-dialog.html using the languages list - template = Mustache.render(SwitchLanguageDialogTemplate, // TODO: add file name to dialog title - $.extend({languages: languages}, Strings)); - // show the dialog and set the handler for OK button + // render the dialog using the languages list and document file name + template = Mustache.render(SwitchLanguageDialogTemplate, + $.extend({ + languages: languages, + fileName: document.file.name + }, Strings)); + // show the dialog and set handler for when it's closed Dialogs.showModalDialogUsingTemplate(template) .done(function (btnId) { if (btnId === Dialogs.DIALOG_BTN_OK) { - editor.document.setLanguage( + document.setLanguage( LanguageManager.getLanguage(selectedLanguage) - ); // TODO: persist the language setting? + ); + } else if (btnId === "reset") { + document.setLanguage( // set to default lang for this file + LanguageManager.getLanguageForPath(document.file.fullPath) + ); } }); // set initial value and change handler for select box @@ -230,7 +237,7 @@ define(function (require, exports, module) { // when language name clicked, open switch language dialog $languageInfo.on("click", function () { - _handleSwitchLanguage(EditorManager.getActiveEditor()); + _handleSwitchLanguage(EditorManager.getActiveEditor().document); }); _onActiveEditorChange(null, EditorManager.getActiveEditor(), null); diff --git a/src/htmlContent/switch-language-dialog.html b/src/htmlContent/switch-language-dialog.html index acaf81141de..10fb0fed8f2 100644 --- a/src/htmlContent/switch-language-dialog.html +++ b/src/htmlContent/switch-language-dialog.html @@ -1,7 +1,7 @@ diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 558f20091dc..62e7b4b5ca1 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -393,8 +393,10 @@ define({ "LANGUAGE_CANCEL" : "Cancel", "LANGUAGE_SYSTEM_DEFAULT" : "System Default", - "SWITCH_LANGUAGE_TITLE" : "Switch Language", + // Switch language (syntax) dialog + "SWITCH_LANGUAGE_TITLE" : "Switch Language for ", "SWITCH_LANGUAGE_MESSAGE" : "Language:", + "SWITCH_LANGUAGE_RESET" : "Reset to Default", /** * Locales From c047b0b56f26acbd6258bb347b7d987287c0c134 Mon Sep 17 00:00:00 2001 From: Jake Stoeffler Date: Wed, 19 Jun 2013 01:26:54 -0400 Subject: [PATCH 03/26] Pass Strings by reference (per #4252) --- src/editor/EditorStatusBar.js | 10 +++++----- src/htmlContent/switch-language-dialog.html | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 157c6f99022..9ec056ac639 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -176,11 +176,11 @@ define(function (require, exports, module) { return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); }); // render the dialog using the languages list and document file name - template = Mustache.render(SwitchLanguageDialogTemplate, - $.extend({ - languages: languages, - fileName: document.file.name - }, Strings)); + template = Mustache.render(SwitchLanguageDialogTemplate, { + languages : languages, + fileName : document.file.name, + Strings : Strings + }); // show the dialog and set handler for when it's closed Dialogs.showModalDialogUsingTemplate(template) .done(function (btnId) { diff --git a/src/htmlContent/switch-language-dialog.html b/src/htmlContent/switch-language-dialog.html index 10fb0fed8f2..899cab6d9bf 100644 --- a/src/htmlContent/switch-language-dialog.html +++ b/src/htmlContent/switch-language-dialog.html @@ -1,11 +1,11 @@ \ No newline at end of file From 50ad04c29e0023f2b1ba583a8602e8d8b0c8fd4c Mon Sep 17 00:00:00 2001 From: Jake Stoeffler Date: Thu, 20 Jun 2013 18:55:50 -0400 Subject: [PATCH 06/26] Move forced flag to Document --- src/document/Document.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index 9332c96f2ab..25fca049db0 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -134,6 +134,13 @@ define(function (require, exports, module) { * @type {FileUtils.LINE_ENDINGS_CRLF|FileUtils.LINE_ENDINGS_LF} */ Document.prototype._lineEndings = null; + + /** + * Whether this document's language was forced (manually selected) or not. + * If true, the language will not change when _updateLanguage() is called. + * @type {boolean} + */ + Document.prototype._languageWasForced = false; /** Add a ref to keep this Document alive */ Document.prototype.addRef = function () { @@ -431,15 +438,13 @@ define(function (require, exports, module) { * null, the language will be set back to the default. */ Document.prototype.forceLanguage = function (language) { - var oldLanguage = this.language; if (language) { - language.forced = true; + var oldLanguage = this.language; + this._languageWasForced = true; this.language = language; $(this).triggerHandler("languageChanged", [oldLanguage, this.language]); } else { // if language was null, reset to default language - if (oldLanguage.forced) { - delete oldLanguage.forced; - } + this._languageWasForced = false; this._updateLanguage(); } }; @@ -449,10 +454,10 @@ define(function (require, exports, module) { * language was forced (set manually by user), don't change it. */ Document.prototype._updateLanguage = function () { - var oldLanguage = this.language; - if (oldLanguage && oldLanguage.forced) { + if (this._languageWasForced) { return; } + var oldLanguage = this.language; this.language = LanguageManager.getLanguageForPath(this.file.fullPath); if (oldLanguage && oldLanguage !== this.language) { $(this).triggerHandler("languageChanged", [oldLanguage, this.language]); From 6e07bdbdb669917ba8d3cebb2cd5f8176627a29a Mon Sep 17 00:00:00 2001 From: Jake Stoeffler Date: Fri, 28 Jun 2013 19:58:41 -0400 Subject: [PATCH 07/26] Setting default language resets force flag --- src/editor/EditorStatusBar.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 6f8edbc3def..f02e7ba3fca 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, window, Mustache */ +/*global define, $, window */ /** * Manages parts of the status bar related to the current editor's state. @@ -186,8 +186,14 @@ define(function (require, exports, module) { // set change handler for select box $languageSelect.on("change", function () { + var selectedLang = LanguageManager.getLanguage($(this).val()), + defaultLang = LanguageManager.getLanguageForPath( + document.file.fullPath + ); + // if default language selected, don't "force"" it + // (passing in null will reset the force flag) document.forceLanguage( - LanguageManager.getLanguage($(this).val()) + selectedLang === defaultLang ? null : selectedLang ); $languageSelect.css("width", "auto"); // unlock width }); From f44a15a3aded2ca772405f3f8b4d9cd76179f94c Mon Sep 17 00:00:00 2001 From: Peter Flynn Date: Wed, 8 Jan 2014 01:05:59 -0800 Subject: [PATCH 08/26] Address code review issues from PR #4276: - Disable language switching for untitled documents - Remove unneeded imports - Switch from deprecated CollectionUtils to lodash - Fix label truncation bug - Fix bug where language change is made redundantly multiple times - Rename some methods for clarity - Add two simple unit tests --- src/document/Document.js | 4 +- src/editor/EditorStatusBar.js | 62 +++++++++++---------- src/styles/brackets.less | 9 ++- test/spec/LanguageManager-test.js | 93 ++++++++++++++++++++++++++++++- 4 files changed, 134 insertions(+), 34 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index c9c46eada5c..06293c6fa7e 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -456,11 +456,11 @@ define(function (require, exports, module) { /** * Overrides the default language of this document and sets it to the given - * language. + * language. This change is not persisted if the document is closed. * @param {?Language} language The language to be set for this document; if * null, the language will be set back to the default. */ - Document.prototype.forceLanguage = function (language) { + Document.prototype.setLanguageOverride = function (language) { if (language) { var oldLanguage = this.language; this._languageWasForced = true; diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 7fdbe95e0cf..07ac1082e94 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -32,10 +32,8 @@ define(function (require, exports, module) { "use strict"; // Load dependent modules - var AppInit = require("utils/AppInit"), - CollectionUtils = require("utils/CollectionUtils"), - DefaultDialogs = require("widgets/DefaultDialogs"), - Dialogs = require("widgets/Dialogs"), + var _ = require("thirdparty/lodash"), + AppInit = require("utils/AppInit"), EditorManager = require("editor/EditorManager"), Editor = require("editor/Editor").Editor, KeyEvent = require("utils/KeyEvent"), @@ -58,7 +56,16 @@ define(function (require, exports, module) { } function _updateLanguageInfo(editor) { - var lang = editor.document.getLanguage(); + var doc = editor.document, + lang = doc.getLanguage(); + + // Ensure width isn't left locked by a previous click of the dropdown (which may not have resulted in a "change" event at the time) + $languageSelect.css("width", "auto"); + + // Setting Untitled documents to non-text mode isn't supported yet, so disable the switcher in that case for now + $languageSelect.prop("disabled", doc.isUntitled()); + + // Only show the current language (full list populated only when dropdown is opened) $languageSelect.empty(); $("