-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Adds preferences that map extensions and filenames to languages. #7588
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ | |
|
||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ | ||
/*global define, $ */ | ||
/*unittests: LanguageManager*/ | ||
|
||
/** | ||
* LanguageManager provides access to the languages supported by Brackets | ||
|
@@ -117,18 +118,46 @@ define(function (require, exports, module) { | |
var CodeMirror = require("thirdparty/CodeMirror2/lib/codemirror"), | ||
Async = require("utils/Async"), | ||
FileUtils = require("file/FileUtils"), | ||
_defaultLanguagesJSON = require("text!language/languages.json"); | ||
|
||
_defaultLanguagesJSON = require("text!language/languages.json"), | ||
_ = require("thirdparty/lodash"), | ||
|
||
// PreferencesManager is loaded near the end of the file | ||
PreferencesManager; | ||
|
||
// State | ||
var _fallbackLanguage = null, | ||
_pendingLanguages = {}, | ||
_languages = {}, | ||
_fileExtensionToLanguageMap = {}, | ||
_fileNameToLanguageMap = {}, | ||
_modeToLanguageMap = {}, | ||
var _fallbackLanguage = null, | ||
_pendingLanguages = {}, | ||
_languages = {}, | ||
_baseFileExtensionToLanguageMap = {}, | ||
_fileExtensionToLanguageMap = Object.create(_baseFileExtensionToLanguageMap), | ||
_fileNameToLanguageMap = {}, | ||
_modeToLanguageMap = {}, | ||
_ready; | ||
|
||
// Constants | ||
|
||
var _EXTENSION_MAP_PREF = "language.fileExtensions", | ||
_NAME_MAP_PREF = "language.fileNames"; | ||
|
||
// Tracking for changes to mappings made by preferences | ||
var _prefState = {}; | ||
|
||
_prefState[_EXTENSION_MAP_PREF] = { | ||
last: {}, | ||
overridden: {}, | ||
add: "addFileExtension", | ||
remove: "removeFileExtension", | ||
get: "getLanguageForExtension" | ||
}; | ||
|
||
_prefState[_NAME_MAP_PREF] = { | ||
last: {}, | ||
overridden: {}, | ||
add: "addFileName", | ||
remove: "removeFileName", | ||
get: "getLanguageForPath" | ||
}; | ||
|
||
// Helper functions | ||
|
||
/** | ||
|
@@ -840,6 +869,90 @@ define(function (require, exports, module) { | |
return result.promise(); | ||
} | ||
|
||
/** | ||
* @private | ||
* | ||
* If a default file extension or name was overridden by a pref, restore it. | ||
* | ||
* @param {string} name Extension or filename that should be restored | ||
* @param {{overridden: string, add: string}} prefState object for the pref that is currently being updated | ||
*/ | ||
function _restoreOverriddenDefault(name, state) { | ||
if (state.overridden[name]) { | ||
var language = getLanguage(state.overridden[name]); | ||
language[state.add](name); | ||
delete state.overridden[name]; | ||
} | ||
} | ||
|
||
/** | ||
* @private | ||
* | ||
* Updates extension and filename mappings from languages based on the current preferences values. | ||
* | ||
* The preferences look like this in a prefs file: | ||
* | ||
* Map *.foo to javascript, *.vm to html | ||
* "language.fileExtensions": { | ||
* "foo": "javascript", | ||
* "vm": "html" | ||
* } | ||
* | ||
* Map "Gemfile" to ruby: | ||
* "language.fileNames": { | ||
* "Gemfile": "ruby" | ||
* } | ||
*/ | ||
function _updateFromPrefs(pref) { | ||
var newMapping = PreferencesManager.get(pref) || {}, | ||
newNames = Object.keys(newMapping), | ||
state = _prefState[pref], | ||
last = state.last, | ||
overridden = state.overridden; | ||
|
||
// Look for added and changed names (extensions or filenames) | ||
newNames.forEach(function (name) { | ||
var language; | ||
if (newMapping[name] !== last[name]) { | ||
if (last[name]) { | ||
language = getLanguage(last[name]); | ||
if (language) { | ||
language[state.remove](name); | ||
|
||
// If this name that was previously mapped was overriding a default | ||
// restore it now. | ||
_restoreOverriddenDefault(name, state); | ||
} | ||
} | ||
|
||
language = exports[state.get](name); | ||
if (language) { | ||
language[state.remove](name); | ||
|
||
// We're removing a name that was defined in Brackets or an extension, | ||
// so keep track of how it used to be mapped. | ||
if (!overridden[name]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will cause to fall back to the first override if it was a second override which the default might have been undefined. If it is important, perhaps Edit: if the file extension was previously unknown to Brackets, I mean. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It shouldn't because it tracks the extensions added by prefs. The flow is something like this:
So, the only overrides that are saved are actually the default mappings since we always restore the default before creating a new mapping. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. I missed that the previous mapping is restored before. |
||
overridden[name] = language.getId(); | ||
} | ||
} | ||
language = getLanguage(newMapping[name]); | ||
if (language) { | ||
language[state.add](name); | ||
} | ||
} | ||
}); | ||
|
||
// Look for removed names (extensions or filenames) | ||
_.difference(Object.keys(last), newNames).forEach(function (name) { | ||
var language = getLanguage(last[name]); | ||
if (language) { | ||
language[state.remove](name); | ||
_restoreOverriddenDefault(name, state); | ||
} | ||
}); | ||
state.last = newMapping; | ||
} | ||
|
||
|
||
// Prevent modes from being overwritten by extensions | ||
_patchCodeMirror(); | ||
|
@@ -881,8 +994,28 @@ define(function (require, exports, module) { | |
|
||
// The fallback language for unknown modes and file extensions | ||
_fallbackLanguage = getLanguage("unknown"); | ||
|
||
// There is a circular dependency between FileUtils and LanguageManager which | ||
// was introduced in 254b01e2f2eebea4416026d0f40d017b8ca6dbc9 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not nice to have the circular dependency between those. FileUtils should not be aware of LanguageManager. Perhaps we should have a dictionary of known extensions (created and maintained by LanguageManager) which would be used by FileUtils. That would help to decouple these two. I don't know if it is worth fixing. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dangoor @busykai @zaggino Oops -- I was the reviewer on that change. Another way to solve this is to move the Let me know if you think that's a good solution and I can make that change as part of the feature work I am currently working on for https://trello.com/c/Sji5hLvW/1219-1s-automatically-ignore-exclude-binary-files. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree that FileUtils shouldn't really depend on LanguageManager (and the circular dependency is not good). I'm not sure what the cleanest fix would be, which is why I didn't attempt the fix. @zaggino any opinion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, there's a typo in that line. Should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a tough one and I didn't really realize that there'll be circular dependency here. But I think that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I opened a code cleanup issue for this: #7631 |
||
// and may be preventing us from importing PreferencesManager (which also | ||
// depends on FileUtils) here. Using the async form of require fixes this. | ||
require(["preferences/PreferencesManager"], function (pm) { | ||
PreferencesManager = pm; | ||
_updateFromPrefs(_EXTENSION_MAP_PREF); | ||
_updateFromPrefs(_NAME_MAP_PREF); | ||
pm.definePreference(_EXTENSION_MAP_PREF, "object").on("change", function () { | ||
_updateFromPrefs(_EXTENSION_MAP_PREF); | ||
}); | ||
pm.definePreference(_NAME_MAP_PREF, "object").on("change", function () { | ||
_updateFromPrefs(_NAME_MAP_PREF); | ||
}); | ||
}); | ||
}); | ||
|
||
// Private for unit tests | ||
exports._EXTENSION_MAP_PREF = _EXTENSION_MAP_PREF; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: making indent of "=" at the same level as public exports will probably make it more readable. |
||
exports._NAME_MAP_PREF = _NAME_MAP_PREF; | ||
|
||
// Public methods | ||
exports.ready = _ready; | ||
exports.defineLanguage = defineLanguage; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to document the structure of pref (preference entry for either language.fileNames or language.fileExtensions)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done