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

Commit

Permalink
Merge pull request #2844 from adobe/dk/less-refactoring
Browse files Browse the repository at this point in the history
Add language extensibility APIs; refactor LESS support out into a default extension using those APIs
  • Loading branch information
peterflynn committed Feb 27, 2013
2 parents 30c2751 + 5a026ec commit fee1311
Show file tree
Hide file tree
Showing 22 changed files with 798 additions and 376 deletions.
40 changes: 37 additions & 3 deletions src/document/DocumentManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


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

/**
* DocumentManager maintains a list of currently 'open' Documents. It also owns the list of files in
Expand Down Expand Up @@ -92,7 +92,8 @@ define(function (require, exports, module) {
Async = require("utils/Async"),
CollectionUtils = require("utils/CollectionUtils"),
PerfUtils = require("utils/PerfUtils"),
Commands = require("command/Commands");
Commands = require("command/Commands"),
LanguageManager = require("language/LanguageManager");

/**
* Unique PreferencesManager clientID
Expand Down Expand Up @@ -598,6 +599,11 @@ define(function (require, exports, module) {
this.file = file;
this.refreshText(rawText, initialTimestamp);

this._updateLanguage();
// TODO: remove this listener when the document object is obsolete.
// But when is this the case? When _refCount === 0?
$(this.file).on("rename", this._updateLanguage.bind(this));

// This is a good point to clean up any old dangling Documents
_gcDocuments();
}
Expand All @@ -613,6 +619,12 @@ define(function (require, exports, module) {
* @type {!FileEntry}
*/
Document.prototype.file = null;

/**
* The Language for this document. Will be resolved by file extension in the constructor
* @type {!Language}
*/
Document.prototype.language = null;

/**
* Whether this document has unsaved changes or not.
Expand Down Expand Up @@ -929,6 +941,28 @@ define(function (require, exports, module) {
return "[Document " + this.file.fullPath + dirtyInfo + editorInfo + refInfo + "]";
};

/**
* Returns the language this document is written in.
* The language returned is based on the file extension.
* @return {Language} An object describing the language used in this document
*/
Document.prototype.getLanguage = function () {
return this.language;
};

/**
* Updates the language according to the file extension
*/
Document.prototype._updateLanguage = function () {
var oldLanguage = this.language;
var ext = PathUtils.filenameExtension(this.file.fullPath);
this.language = LanguageManager.getLanguageForFileExtension(ext);

if (oldLanguage && oldLanguage !== this.language) {
$(this).triggerHandler("languageChanged", [oldLanguage, this.language]);
}
};

/**
* Gets an existing open Document for the given file, or creates a new one if the Document is
* not currently open ('open' means referenced by the UI somewhere). Always use this method to
Expand Down Expand Up @@ -1167,7 +1201,7 @@ define(function (require, exports, module) {
// Send a "fileNameChanged" event. This will trigger the views to update.
$(exports).triggerHandler("fileNameChange", [oldName, newName]);
}

// Define public API
exports.Document = Document;
exports.getCurrentDocument = getCurrentDocument;
Expand Down
55 changes: 31 additions & 24 deletions src/editor/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,11 @@ define(function (require, exports, module) {
* @param {!boolean} makeMasterEditor If true, this Editor will set itself as the (secret) "master"
* Editor for the Document. If false, this Editor will attach to the Document as a "slave"/
* secondary editor.
* @param {!(string|Object)} mode Syntax-highlighting language mode; "" means plain-text mode.
* May either be a string naming the mode, or an object containing a "name" property
* naming the mode along with configuration options required by the mode.
* See {@link EditorUtils#getModeFromFileExtension()}.
* @param {!jQueryObject} container Container to add the editor to.
* @param {{startLine: number, endLine: number}=} range If specified, range of lines within the document
* to display in this editor. Inclusive.
*/
function Editor(document, makeMasterEditor, mode, container, range) {
function Editor(document, makeMasterEditor, container, range) {
var self = this;

_instances.push(this);
Expand All @@ -308,8 +304,12 @@ define(function (require, exports, module) {
// store this-bound version of listeners so we can remove them later
this._handleDocumentChange = this._handleDocumentChange.bind(this);
this._handleDocumentDeleted = this._handleDocumentDeleted.bind(this);
this._handleDocumentLanguageChanged = this._handleDocumentLanguageChanged.bind(this);
$(document).on("change", this._handleDocumentChange);
$(document).on("deleted", this._handleDocumentDeleted);
$(document).on("languageChanged", this._handleDocumentLanguageChanged);

var mode = this._getModeFromDocument();

// (if makeMasterEditor, we attach the Doc back to ourselves below once we're fully initialized)

Expand Down Expand Up @@ -346,13 +346,6 @@ define(function (require, exports, module) {
"Cmd-Left": "goLineStartSmart"
};

// We'd like null/"" to mean plain text mode. CodeMirror defaults to plaintext for any
// unrecognized mode, but it complains on the console in that fallback case: so, convert
// here so we're always explicit, avoiding console noise.
if (!mode) {
mode = "text/plain";
}

// Create the CodeMirror instance
// (note: CodeMirror doesn't actually require using 'new', but jslint complains without it)
this._codeMirror = new CodeMirror(container, {
Expand Down Expand Up @@ -436,6 +429,7 @@ define(function (require, exports, module) {
this.document.releaseRef();
$(this.document).off("change", this._handleDocumentChange);
$(this.document).off("deleted", this._handleDocumentDeleted);
$(this.document).off("languageChanged", this._handleDocumentLanguageChanged);

if (this._visibleRange) { // TextRange also refs the Document
this._visibleRange.dispose();
Expand All @@ -453,6 +447,18 @@ define(function (require, exports, module) {
});
};

/**
* Determine the mode to use from the document's language
* Uses "text/plain" if the language does not define a mode
* @return string The mode to use
*/
Editor.prototype._getModeFromDocument = function () {
// We'd like undefined/null/"" to mean plain text mode. CodeMirror defaults to plaintext for any
// unrecognized mode, but it complains on the console in that fallback case: so, convert
// here so we're always explicit, avoiding console noise.
return this.document.getLanguage().mode || "text/plain";
};


/**
* Selects all text and maintains the current scroll position.
Expand Down Expand Up @@ -598,6 +604,13 @@ define(function (require, exports, module) {
$(this).triggerHandler("lostContent", [event]);
};

/**
* Responds to language changes, for instance when the file extension is changed.
*/
Editor.prototype._handleDocumentLanguageChanged = function (event) {
this._codeMirror.setOption("mode", this._getModeFromDocument());
};


/**
* Install event handlers on the CodeMirror instance, translating them into
Expand Down Expand Up @@ -1195,7 +1208,7 @@ define(function (require, exports, module) {
*
* @return {?(Object|string)} Name of syntax-highlighting mode, or object containing a "name" property
* naming the mode along with configuration options required by the mode.
* See {@link EditorUtils#getModeFromFileExtension()}.
* See {@link Languages#getLanguageForFileExtension()} and {@link Language#mode}.
*/
Editor.prototype.getModeForSelection = function () {
// Check for mixed mode info
Expand All @@ -1221,25 +1234,19 @@ define(function (require, exports, module) {
}
};

Editor.prototype.getLanguageForSelection = function () {
return this.document.getLanguage().getLanguageForMode(this.getModeForSelection());
};

/**
* Gets the syntax-highlighting mode for the document.
*
* @return {Object|String} Object or Name of syntax-highlighting mode; see {@link EditorUtils#getModeFromFileExtension()}.
* @return {Object|String} Object or Name of syntax-highlighting mode; see {@link Languages#getLanguageForFileExtension()} and {@link Language#mode}.
*/
Editor.prototype.getModeForDocument = function () {
return this._codeMirror.getOption("mode");
};

/**
* Sets the syntax-highlighting mode for the document.
*
* @param {(string|Object)} mode Name of syntax highlighting mode, or object containing a "name"
* property naming the mode along with configuration options required by the mode.
*/
Editor.prototype.setModeForDocument = function (mode) {
this._codeMirror.setOption("mode", mode);
};

/**
* The Document we're bound to
* @type {!Document}
Expand Down
65 changes: 28 additions & 37 deletions src/editor/EditorCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ define(function (require, exports, module) {
StringUtils = require("utils/StringUtils"),
TokenUtils = require("utils/TokenUtils");


/**
* List of constants
*/
Expand All @@ -55,14 +54,15 @@ define(function (require, exports, module) {
* @param {!number} endLine - valid line inside the document
* @return {boolean} true if there is at least one uncommented line
*/
function _containsUncommented(editor, startLine, endLine) {
function _containsUncommented(editor, startLine, endLine, prefix) {
var lineExp = new RegExp("^\\s*" + StringUtils.regexEscape(prefix));
var containsUncommented = false;
var i;
var line;
for (i = startLine; i <= endLine; i++) {
line = editor.document.getLine(i);
// A line is commented out if it starts with 0-N whitespace chars, then "//"
if (!line.match(/^\s*\/\//) && line.match(/\S/)) {
if (!line.match(lineExp) && line.match(/\S/)) {
containsUncommented = true;
break;
}
Expand All @@ -75,11 +75,10 @@ define(function (require, exports, module) {
* and cursor position. Applies to currently focused Editor.
*
* If all non-whitespace lines are already commented out, then we uncomment; otherwise we comment
* out. Commenting out adds "//" to at column 0 of every line. Uncommenting removes the first "//"
* out. Commenting out adds the prefix at column 0 of every line. Uncommenting removes the first prefix
* on each line (if any - empty lines might not have one).
*/
function lineCommentSlashSlash(editor) {

function lineCommentPrefix(editor, prefix) {
var doc = editor.document;
var sel = editor.getSelection();
var startLine = sel.start.line;
Expand All @@ -96,7 +95,7 @@ define(function (require, exports, module) {
// Decide if we're commenting vs. un-commenting
// Are there any non-blank lines that aren't commented out? (We ignore blank lines because
// some editors like Sublime don't comment them out)
var containsUncommented = _containsUncommented(editor, startLine, endLine);
var containsUncommented = _containsUncommented(editor, startLine, endLine, prefix);
var i;
var line;
var updateSelection = false;
Expand All @@ -107,7 +106,7 @@ define(function (require, exports, module) {
if (containsUncommented) {
// Comment out - prepend "//" to each line
for (i = startLine; i <= endLine; i++) {
doc.replaceRange("//", {line: i, ch: 0});
doc.replaceRange(prefix, {line: i, ch: 0});
}

// Make sure selection includes "//" that was added at start of range
Expand All @@ -119,9 +118,9 @@ define(function (require, exports, module) {
// Uncomment - remove first "//" on each line (if any)
for (i = startLine; i <= endLine; i++) {
line = doc.getLine(i);
var commentI = line.indexOf("//");
var commentI = line.indexOf(prefix);
if (commentI !== -1) {
doc.replaceRange("", {line: i, ch: commentI}, {line: i, ch: commentI + 2});
doc.replaceRange("", {line: i, ch: commentI}, {line: i, ch: commentI + prefix.length});
}
}
}
Expand Down Expand Up @@ -205,11 +204,11 @@ define(function (require, exports, module) {
* the lines in the selection are line-commented.
*
* @param {!Editor} editor
* @param {!String} prefix
* @param {!String} suffix
* @param {boolean=} slashComment - true if the mode also supports "//" comments
* @param {!string} prefix, e.g. "<!--"
* @param {!string} suffix, e.g. "-->"
* @param {?string} linePrefix, e.g. "//"
*/
function blockCommentPrefixSuffix(editor, prefix, suffix, slashComment) {
function blockCommentPrefixSuffix(editor, prefix, suffix, linePrefix) {

var doc = editor.document,
sel = editor.getSelection(),
Expand All @@ -218,7 +217,7 @@ define(function (require, exports, module) {
endCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.end.line, ch: sel.end.ch}),
prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"),
suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"),
lineExp = new RegExp("^\/\/"),
lineExp = linePrefix ? new RegExp("^" + StringUtils.regexEscape(linePrefix)) : null,
prefixPos = null,
suffixPos = null,
canComment = false,
Expand All @@ -234,7 +233,7 @@ define(function (require, exports, module) {
}

// Check if we should just do a line uncomment (if all lines in the selection are commented).
if (slashComment && (ctx.token.string.match(lineExp) || endCtx.token.string.match(lineExp))) {
if (lineExp && (ctx.token.string.match(lineExp) || endCtx.token.string.match(lineExp))) {
var startCtxIndex = editor.indexFromPos({line: ctx.pos.line, ch: ctx.token.start});
var endCtxIndex = editor.indexFromPos({line: endCtx.pos.line, ch: endCtx.token.start + endCtx.token.string.length});

Expand All @@ -256,7 +255,7 @@ define(function (require, exports, module) {
}

// Find if all the lines are line-commented.
if (!_containsUncommented(editor, sel.start.line, endLine)) {
if (!_containsUncommented(editor, sel.start.line, endLine, linePrefix)) {
lineUncomment = true;

// Block-comment in all the other cases
Expand Down Expand Up @@ -328,7 +327,7 @@ define(function (require, exports, module) {
return;

} else if (lineUncomment) {
lineCommentSlashSlash(editor);
lineCommentPrefix(editor, linePrefix);

} else {
doc.batchOperation(function () {
Expand Down Expand Up @@ -438,12 +437,11 @@ define(function (require, exports, module) {
result = result && _findNextBlockComment(ctx, selEnd, prefixExp);

if (className === "comment" || result || isLineSelection) {
blockCommentPrefixSuffix(editor, prefix, suffix, false);

blockCommentPrefixSuffix(editor, prefix, suffix);
} else {
// Set the new selection and comment it
editor.setSelection(selStart, selEnd);
blockCommentPrefixSuffix(editor, prefix, suffix, false);
blockCommentPrefixSuffix(editor, prefix, suffix);

// Restore the old selection taking into account the prefix change
if (isMultipleLine) {
Expand All @@ -468,14 +466,10 @@ define(function (require, exports, module) {
return;
}

var mode = editor.getModeForSelection();
var language = editor.getLanguageForSelection();

if (mode === "javascript" || mode === "less") {
blockCommentPrefixSuffix(editor, "/*", "*/", true);
} else if (mode === "css") {
blockCommentPrefixSuffix(editor, "/*", "*/", false);
} else if (mode === "html") {
blockCommentPrefixSuffix(editor, "<!--", "-->", false);
if (language.blockComment) {
blockCommentPrefixSuffix(editor, language.blockComment.prefix, language.blockComment.suffix, language.lineComment ? language.lineComment.prefix : null);
}
}

Expand All @@ -489,15 +483,12 @@ define(function (require, exports, module) {
return;
}

var mode = editor.getModeForSelection();
var language = editor.getLanguageForSelection();

// Currently we only support languages with "//" commenting
if (mode === "javascript" || mode === "less") {
lineCommentSlashSlash(editor);
} else if (mode === "css") {
lineCommentPrefixSuffix(editor, "/*", "*/");
} else if (mode === "html") {
lineCommentPrefixSuffix(editor, "<!--", "-->");
if (language.lineComment) {
lineCommentPrefix(editor, language.lineComment.prefix);
} else if (language.blockComment) {
lineCommentPrefixSuffix(editor, language.blockComment.prefix, language.blockComment.suffix);
}
}

Expand Down Expand Up @@ -733,7 +724,7 @@ define(function (require, exports, module) {
CommandManager.register(Strings.CMD_LINE_UP, Commands.EDIT_LINE_UP, moveLineUp);
CommandManager.register(Strings.CMD_LINE_DOWN, Commands.EDIT_LINE_DOWN, moveLineDown);
CommandManager.register(Strings.CMD_SELECT_LINE, Commands.EDIT_SELECT_LINE, selectLine);

CommandManager.register(Strings.CMD_UNDO, Commands.EDIT_UNDO, handleUndo);
CommandManager.register(Strings.CMD_REDO, Commands.EDIT_REDO, handleRedo);
CommandManager.register(Strings.CMD_CUT, Commands.EDIT_CUT, ignoreCommand);
Expand Down
Loading

0 comments on commit fee1311

Please sign in to comment.