Bug 919978 - Make StyleEditor use CodeMirror, r=anton, msucan
This commit is contained in:
@@ -862,7 +862,7 @@ FilterView.prototype = {
|
|||||||
_performLineSearch: function(aLine) {
|
_performLineSearch: function(aLine) {
|
||||||
// Make sure we're actually searching for a valid line.
|
// Make sure we're actually searching for a valid line.
|
||||||
if (aLine) {
|
if (aLine) {
|
||||||
DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 });
|
DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 }, "center");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1497,6 +1497,7 @@ FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototyp
|
|||||||
DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, {
|
DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, {
|
||||||
charOffset: scriptOffset,
|
charOffset: scriptOffset,
|
||||||
columnOffset: actualLocation.start.column,
|
columnOffset: actualLocation.start.column,
|
||||||
|
align: "center",
|
||||||
noDebug: true
|
noDebug: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -457,7 +457,8 @@ let DebuggerView = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!aFlags.noCaret) {
|
if (!aFlags.noCaret) {
|
||||||
this.editor.setCursor({ line: aLine -1, ch: aFlags.columnOffset || 0 });
|
this.editor.setCursor({ line: aLine -1, ch: aFlags.columnOffset || 0 },
|
||||||
|
aFlags.align);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!aFlags.noDebug) {
|
if (!aFlags.noDebug) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ browser.jar:
|
|||||||
content/browser/devtools/codemirror/htmlmixed.js (sourceeditor/codemirror/htmlmixed.js)
|
content/browser/devtools/codemirror/htmlmixed.js (sourceeditor/codemirror/htmlmixed.js)
|
||||||
content/browser/devtools/codemirror/activeline.js (sourceeditor/codemirror/activeline.js)
|
content/browser/devtools/codemirror/activeline.js (sourceeditor/codemirror/activeline.js)
|
||||||
content/browser/devtools/codemirror/matchbrackets.js (sourceeditor/codemirror/matchbrackets.js)
|
content/browser/devtools/codemirror/matchbrackets.js (sourceeditor/codemirror/matchbrackets.js)
|
||||||
|
content/browser/devtools/codemirror/closebrackets.js (sourceeditor/codemirror/closebrackets.js)
|
||||||
content/browser/devtools/codemirror/comment.js (sourceeditor/codemirror/comment.js)
|
content/browser/devtools/codemirror/comment.js (sourceeditor/codemirror/comment.js)
|
||||||
content/browser/devtools/codemirror/searchcursor.js (sourceeditor/codemirror/search/searchcursor.js)
|
content/browser/devtools/codemirror/searchcursor.js (sourceeditor/codemirror/search/searchcursor.js)
|
||||||
content/browser/devtools/codemirror/search.js (sourceeditor/codemirror/search/search.js)
|
content/browser/devtools/codemirror/search.js (sourceeditor/codemirror/search/search.js)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ in the LICENSE file:
|
|||||||
* dialog/dialog.js
|
* dialog/dialog.js
|
||||||
* javascript.js
|
* javascript.js
|
||||||
* matchbrackets.js
|
* matchbrackets.js
|
||||||
|
* closebrackets.js
|
||||||
* search/match-highlighter.js
|
* search/match-highlighter.js
|
||||||
* search/search.js
|
* search/search.js
|
||||||
* search/searchcursor.js
|
* search/searchcursor.js
|
||||||
|
|||||||
82
browser/devtools/sourceeditor/codemirror/closebrackets.js
Normal file
82
browser/devtools/sourceeditor/codemirror/closebrackets.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
(function() {
|
||||||
|
var DEFAULT_BRACKETS = "()[]{}''\"\"";
|
||||||
|
var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
|
||||||
|
var SPACE_CHAR_REGEX = /\s/;
|
||||||
|
|
||||||
|
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
|
||||||
|
if (old != CodeMirror.Init && old)
|
||||||
|
cm.removeKeyMap("autoCloseBrackets");
|
||||||
|
if (!val) return;
|
||||||
|
var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER;
|
||||||
|
if (typeof val == "string") pairs = val;
|
||||||
|
else if (typeof val == "object") {
|
||||||
|
if (val.pairs != null) pairs = val.pairs;
|
||||||
|
if (val.explode != null) explode = val.explode;
|
||||||
|
}
|
||||||
|
var map = buildKeymap(pairs);
|
||||||
|
if (explode) map.Enter = buildExplodeHandler(explode);
|
||||||
|
cm.addKeyMap(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
function charsAround(cm, pos) {
|
||||||
|
var str = cm.getRange(CodeMirror.Pos(pos.line, pos.ch - 1),
|
||||||
|
CodeMirror.Pos(pos.line, pos.ch + 1));
|
||||||
|
return str.length == 2 ? str : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildKeymap(pairs) {
|
||||||
|
var map = {
|
||||||
|
name : "autoCloseBrackets",
|
||||||
|
Backspace: function(cm) {
|
||||||
|
if (cm.somethingSelected()) return CodeMirror.Pass;
|
||||||
|
var cur = cm.getCursor(), around = charsAround(cm, cur);
|
||||||
|
if (around && pairs.indexOf(around) % 2 == 0)
|
||||||
|
cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
|
||||||
|
else
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var closingBrackets = "";
|
||||||
|
for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
|
||||||
|
if (left != right) closingBrackets += right;
|
||||||
|
function surround(cm) {
|
||||||
|
var selection = cm.getSelection();
|
||||||
|
cm.replaceSelection(left + selection + right);
|
||||||
|
}
|
||||||
|
function maybeOverwrite(cm) {
|
||||||
|
var cur = cm.getCursor(), ahead = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
|
||||||
|
if (ahead != right || cm.somethingSelected()) return CodeMirror.Pass;
|
||||||
|
else cm.execCommand("goCharRight");
|
||||||
|
}
|
||||||
|
map["'" + left + "'"] = function(cm) {
|
||||||
|
if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment")
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
if (cm.somethingSelected()) return surround(cm);
|
||||||
|
if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return;
|
||||||
|
var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1);
|
||||||
|
var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch), curChar = cur.ch > 0 ? line.charAt(cur.ch - 1) : "";
|
||||||
|
if (left == right && CodeMirror.isWordChar(curChar))
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar))
|
||||||
|
cm.replaceSelection(left + right, {head: ahead, anchor: ahead});
|
||||||
|
else
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
};
|
||||||
|
if (left != right) map["'" + right + "'"] = maybeOverwrite;
|
||||||
|
})(pairs.charAt(i), pairs.charAt(i + 1));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExplodeHandler(pairs) {
|
||||||
|
return function(cm) {
|
||||||
|
var cur = cm.getCursor(), around = charsAround(cm, cur);
|
||||||
|
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
||||||
|
cm.operation(function() {
|
||||||
|
var newPos = CodeMirror.Pos(cur.line + 1, 0);
|
||||||
|
cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input");
|
||||||
|
cm.indentLine(cur.line + 1, null, true);
|
||||||
|
cm.indentLine(cur.line + 2, null, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -62,25 +62,27 @@ function getSearchCursor(cm, query, pos) {
|
|||||||
* Otherwise, creates a new search and selects the first
|
* Otherwise, creates a new search and selects the first
|
||||||
* result.
|
* result.
|
||||||
*/
|
*/
|
||||||
function doSearch(cm, rev, query) {
|
function doSearch(ctx, rev, query) {
|
||||||
|
let { cm } = ctx;
|
||||||
let state = getSearchState(cm);
|
let state = getSearchState(cm);
|
||||||
|
|
||||||
if (state.query)
|
if (state.query)
|
||||||
return searchNext(cm, rev);
|
return searchNext(ctx, rev);
|
||||||
|
|
||||||
cm.operation(function () {
|
cm.operation(function () {
|
||||||
if (state.query) return;
|
if (state.query) return;
|
||||||
|
|
||||||
state.query = query;
|
state.query = query;
|
||||||
state.posFrom = state.posTo = { line: 0, ch: 0 };
|
state.posFrom = state.posTo = { line: 0, ch: 0 };
|
||||||
searchNext(cm, rev);
|
searchNext(ctx, rev);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects the next result of a saved search.
|
* Selects the next result of a saved search.
|
||||||
*/
|
*/
|
||||||
function searchNext(cm, rev) {
|
function searchNext(ctx, rev) {
|
||||||
|
let { cm, ed } = ctx;
|
||||||
cm.operation(function () {
|
cm.operation(function () {
|
||||||
let state = getSearchState(cm)
|
let state = getSearchState(cm)
|
||||||
let cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
let cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
||||||
@@ -92,6 +94,7 @@ function searchNext(cm, rev) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ed.alignLine(cursor.from().line, "center");
|
||||||
cm.setSelection(cursor.from(), cursor.to());
|
cm.setSelection(cursor.from(), cursor.to());
|
||||||
state.posFrom = cursor.from();
|
state.posFrom = cursor.from();
|
||||||
state.posTo = cursor.to();
|
state.posTo = cursor.to();
|
||||||
@@ -236,25 +239,22 @@ function clearDebugLocation(ctx) {
|
|||||||
* Starts a new search.
|
* Starts a new search.
|
||||||
*/
|
*/
|
||||||
function find(ctx, query) {
|
function find(ctx, query) {
|
||||||
let { cm } = ctx;
|
clearSearch(ctx.cm);
|
||||||
clearSearch(cm);
|
doSearch(ctx, false, query);
|
||||||
doSearch(cm, false, query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the next item based on the currently saved search.
|
* Finds the next item based on the currently saved search.
|
||||||
*/
|
*/
|
||||||
function findNext(ctx, query) {
|
function findNext(ctx, query) {
|
||||||
let { cm } = ctx;
|
doSearch(ctx, false, query);
|
||||||
doSearch(cm, false, query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the previous item based on the currently saved search.
|
* Finds the previous item based on the currently saved search.
|
||||||
*/
|
*/
|
||||||
function findPrev(ctx, query) {
|
function findPrev(ctx, query) {
|
||||||
let { cm } = ctx;
|
doSearch(ctx, true, query);
|
||||||
doSearch(cm, true, query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ const EXPAND_TAB = "devtools.editor.expandtab";
|
|||||||
const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
|
const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
|
||||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
|
|
||||||
|
// Maximum allowed margin (in number of lines) from top or bottom of the editor
|
||||||
|
// while shifting to a line which was initially out of view.
|
||||||
|
const MAX_VERTICAL_OFFSET = 3;
|
||||||
|
|
||||||
const promise = require("sdk/core/promise");
|
const promise = require("sdk/core/promise");
|
||||||
const events = require("devtools/shared/event-emitter");
|
const events = require("devtools/shared/event-emitter");
|
||||||
|
|
||||||
@@ -34,6 +38,7 @@ const CM_SCRIPTS = [
|
|||||||
"chrome://browser/content/devtools/codemirror/searchcursor.js",
|
"chrome://browser/content/devtools/codemirror/searchcursor.js",
|
||||||
"chrome://browser/content/devtools/codemirror/search.js",
|
"chrome://browser/content/devtools/codemirror/search.js",
|
||||||
"chrome://browser/content/devtools/codemirror/matchbrackets.js",
|
"chrome://browser/content/devtools/codemirror/matchbrackets.js",
|
||||||
|
"chrome://browser/content/devtools/codemirror/closebrackets.js",
|
||||||
"chrome://browser/content/devtools/codemirror/comment.js",
|
"chrome://browser/content/devtools/codemirror/comment.js",
|
||||||
"chrome://browser/content/devtools/codemirror/javascript.js",
|
"chrome://browser/content/devtools/codemirror/javascript.js",
|
||||||
"chrome://browser/content/devtools/codemirror/xml.js",
|
"chrome://browser/content/devtools/codemirror/xml.js",
|
||||||
@@ -59,7 +64,6 @@ const CM_IFRAME =
|
|||||||
const CM_MAPPING = [
|
const CM_MAPPING = [
|
||||||
"focus",
|
"focus",
|
||||||
"hasFocus",
|
"hasFocus",
|
||||||
"setCursor",
|
|
||||||
"getCursor",
|
"getCursor",
|
||||||
"somethingSelected",
|
"somethingSelected",
|
||||||
"setSelection",
|
"setSelection",
|
||||||
@@ -78,6 +82,8 @@ const CM_JUMP_DIALOG = [
|
|||||||
+ " <input type=text style='width: 10em'/>"
|
+ " <input type=text style='width: 10em'/>"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const { cssProperties, cssValues, cssColors } = getCSSKeywords();
|
||||||
|
|
||||||
const editors = new WeakMap();
|
const editors = new WeakMap();
|
||||||
|
|
||||||
Editor.modes = {
|
Editor.modes = {
|
||||||
@@ -192,7 +198,21 @@ Editor.prototype = {
|
|||||||
CM_SCRIPTS.forEach((url) =>
|
CM_SCRIPTS.forEach((url) =>
|
||||||
Services.scriptloader.loadSubScript(url, win, "utf8"));
|
Services.scriptloader.loadSubScript(url, win, "utf8"));
|
||||||
|
|
||||||
// Create a CodeMirror instance add support for context menus and
|
// Replace the propertyKeywords, colorKeywords and valueKeywords
|
||||||
|
// properties of the CSS MIME type with the values provided by Gecko.
|
||||||
|
let cssSpec = win.CodeMirror.resolveMode("text/css");
|
||||||
|
cssSpec.propertyKeywords = cssProperties;
|
||||||
|
cssSpec.colorKeywords = cssColors;
|
||||||
|
cssSpec.valueKeywords = cssValues;
|
||||||
|
win.CodeMirror.defineMIME("text/css", cssSpec);
|
||||||
|
|
||||||
|
let scssSpec = win.CodeMirror.resolveMode("text/x-scss");
|
||||||
|
scssSpec.propertyKeywords = cssProperties;
|
||||||
|
scssSpec.colorKeywords = cssColors;
|
||||||
|
scssSpec.valueKeywords = cssValues;
|
||||||
|
win.CodeMirror.defineMIME("text/x-scss", scssSpec);
|
||||||
|
|
||||||
|
// Create a CodeMirror instance add support for context menus,
|
||||||
// overwrite the default controller (otherwise items in the top and
|
// overwrite the default controller (otherwise items in the top and
|
||||||
// context menus won't work).
|
// context menus won't work).
|
||||||
|
|
||||||
@@ -434,6 +454,67 @@ Editor.prototype = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first visible line number in the editor.
|
||||||
|
*/
|
||||||
|
getFirstVisibleLine: function () {
|
||||||
|
let cm = editors.get(this);
|
||||||
|
return cm.lineAtHeight(0, "local");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls the view such that the given line number is the first visible line.
|
||||||
|
*/
|
||||||
|
setFirstVisibleLine: function (line) {
|
||||||
|
let cm = editors.get(this);
|
||||||
|
let { top } = cm.charCoords({line: line, ch: 0}, "local");
|
||||||
|
cm.scrollTo(0, top);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cursor to the specified {line, ch} position with an additional
|
||||||
|
* option to align the line at the "top", "center" or "bottom" of the editor
|
||||||
|
* with "top" being default value.
|
||||||
|
*/
|
||||||
|
setCursor: function ({line, ch}, align) {
|
||||||
|
let cm = editors.get(this);
|
||||||
|
this.alignLine(line, align);
|
||||||
|
cm.setCursor({line: line, ch: ch});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aligns the provided line to either "top", "center" or "bottom" of the
|
||||||
|
* editor view with a maximum margin of MAX_VERTICAL_OFFSET lines from top or
|
||||||
|
* bottom.
|
||||||
|
*/
|
||||||
|
alignLine: function(line, align) {
|
||||||
|
let cm = editors.get(this);
|
||||||
|
let from = cm.lineAtHeight(0, "page");
|
||||||
|
let to = cm.lineAtHeight(cm.getWrapperElement().clientHeight, "page");
|
||||||
|
let linesVisible = to - from;
|
||||||
|
let halfVisible = Math.round(linesVisible/2);
|
||||||
|
|
||||||
|
// If the target line is in view, skip the vertical alignment part.
|
||||||
|
if (line <= to && line >= from) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the offset so that the line always falls in the upper half
|
||||||
|
// of visible lines (lower half for bottom aligned).
|
||||||
|
// MAX_VERTICAL_OFFSET is the maximum allowed value.
|
||||||
|
let offset = Math.min(halfVisible, MAX_VERTICAL_OFFSET);
|
||||||
|
|
||||||
|
let topLine = {
|
||||||
|
"center": Math.max(line - halfVisible, 0),
|
||||||
|
"bottom": Math.max(line - linesVisible + offset, 0),
|
||||||
|
"top": Math.max(line - offset, 0)
|
||||||
|
}[align || "top"] || offset;
|
||||||
|
|
||||||
|
// Bringing down the topLine to total lines in the editor if exceeding.
|
||||||
|
topLine = Math.min(topLine, this.lineCount());
|
||||||
|
this.setFirstVisibleLine(topLine);
|
||||||
|
},
|
||||||
|
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
this.container = null;
|
this.container = null;
|
||||||
this.config = null;
|
this.config = null;
|
||||||
@@ -452,6 +533,44 @@ CM_MAPPING.forEach(function (name) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Since Gecko already provide complete and up to date list of CSS property
|
||||||
|
// names, values and color names, we compute them so that they can replace
|
||||||
|
// the ones used in CodeMirror while initiating an editor object. This is done
|
||||||
|
// here instead of the file codemirror/css.js so as to leave that file untouched
|
||||||
|
// and easily upgradable.
|
||||||
|
function getCSSKeywords() {
|
||||||
|
function keySet(array) {
|
||||||
|
var keys = {};
|
||||||
|
for (var i = 0; i < array.length; ++i) {
|
||||||
|
keys[array[i]] = true;
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
|
||||||
|
.getService(Ci.inIDOMUtils);
|
||||||
|
let cssProperties = domUtils.getCSSPropertyNames(domUtils.INCLUDE_ALIASES);
|
||||||
|
let cssColors = {};
|
||||||
|
let cssValues = {};
|
||||||
|
cssProperties.forEach(property => {
|
||||||
|
if (property.contains("color")) {
|
||||||
|
domUtils.getCSSValuesForProperty(property).forEach(value => {
|
||||||
|
cssColors[value] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
domUtils.getCSSValuesForProperty(property).forEach(value => {
|
||||||
|
cssValues[value] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cssProperties: keySet(cssProperties),
|
||||||
|
cssValues: cssValues,
|
||||||
|
cssColors: cssColors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a controller object that can be used for
|
* Returns a controller object that can be used for
|
||||||
* editor-specific commands such as find, jump to line,
|
* editor-specific commands such as find, jump to line,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ StyleEditorUI.prototype = {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this.editors.some((editor) => {
|
return this.editors.some((editor) => {
|
||||||
return editor.sourceEditor && editor.sourceEditor.dirty;
|
return editor.sourceEditor && !editor.sourceEditor.isClean();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -151,8 +151,8 @@ StyleEditorUI.prototype = {
|
|||||||
// remember selected sheet and line number for next load
|
// remember selected sheet and line number for next load
|
||||||
if (this.selectedEditor && this.selectedEditor.sourceEditor) {
|
if (this.selectedEditor && this.selectedEditor.sourceEditor) {
|
||||||
let href = this.selectedEditor.styleSheet.href;
|
let href = this.selectedEditor.styleSheet.href;
|
||||||
let {line, col} = this.selectedEditor.sourceEditor.getCaretPosition();
|
let {line, ch} = this.selectedEditor.sourceEditor.getCursor();
|
||||||
this.selectStyleSheet(href, line, col);
|
this.selectStyleSheet(href, line, ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._clearStyleSheetEditors();
|
this._clearStyleSheetEditors();
|
||||||
@@ -365,7 +365,7 @@ StyleEditorUI.prototype = {
|
|||||||
col = col || 0;
|
col = col || 0;
|
||||||
|
|
||||||
editor.getSourceEditor().then(() => {
|
editor.getSourceEditor().then(() => {
|
||||||
editor.sourceEditor.setCaretPosition(line, col);
|
editor.sourceEditor.setCursor({line: line, ch: col});
|
||||||
});
|
});
|
||||||
|
|
||||||
this._view.activeSummary = editor.summary;
|
this._view.activeSummary = editor.summary;
|
||||||
|
|||||||
@@ -11,21 +11,26 @@ const Cc = Components.classes;
|
|||||||
const Ci = Components.interfaces;
|
const Ci = Components.interfaces;
|
||||||
const Cu = Components.utils;
|
const Cu = Components.utils;
|
||||||
|
|
||||||
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
|
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||||
|
const Editor = require("devtools/sourceeditor/editor");
|
||||||
|
const promise = require("sdk/core/promise");
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm");
|
|
||||||
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
|
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
|
||||||
|
|
||||||
|
|
||||||
const SAVE_ERROR = "error-save";
|
const SAVE_ERROR = "error-save";
|
||||||
|
|
||||||
// max update frequency in ms (avoid potential typing lag and/or flicker)
|
// max update frequency in ms (avoid potential typing lag and/or flicker)
|
||||||
// @see StyleEditor.updateStylesheet
|
// @see StyleEditor.updateStylesheet
|
||||||
const UPDATE_STYLESHEET_THROTTLE_DELAY = 500;
|
const UPDATE_STYLESHEET_THROTTLE_DELAY = 500;
|
||||||
|
|
||||||
|
function ctrl(k) {
|
||||||
|
return (Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + k;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StyleSheetEditor controls the editor linked to a particular StyleSheet
|
* StyleSheetEditor controls the editor linked to a particular StyleSheet
|
||||||
* object.
|
* object.
|
||||||
@@ -58,7 +63,10 @@ function StyleSheetEditor(styleSheet, win, file, isNew) {
|
|||||||
|
|
||||||
this._state = { // state to use when inputElement attaches
|
this._state = { // state to use when inputElement attaches
|
||||||
text: "",
|
text: "",
|
||||||
selection: {start: 0, end: 0},
|
selection: {
|
||||||
|
start: {line: 0, ch: 0},
|
||||||
|
end: {line: 0, ch: 0}
|
||||||
|
},
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
topIndex: 0, // the first visible line
|
topIndex: 0, // the first visible line
|
||||||
};
|
};
|
||||||
@@ -92,7 +100,7 @@ StyleSheetEditor.prototype = {
|
|||||||
* Whether there are unsaved changes in the editor
|
* Whether there are unsaved changes in the editor
|
||||||
*/
|
*/
|
||||||
get unsaved() {
|
get unsaved() {
|
||||||
return this._sourceEditor && this._sourceEditor.dirty;
|
return this._sourceEditor && !this._sourceEditor.isClean();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,21 +208,20 @@ StyleSheetEditor.prototype = {
|
|||||||
load: function(inputElement) {
|
load: function(inputElement) {
|
||||||
this._inputElement = inputElement;
|
this._inputElement = inputElement;
|
||||||
|
|
||||||
let sourceEditor = new SourceEditor();
|
|
||||||
let config = {
|
let config = {
|
||||||
initialText: this._state.text,
|
value: this._state.text,
|
||||||
showLineNumbers: true,
|
lineNumbers: true,
|
||||||
mode: SourceEditor.MODES.CSS,
|
mode: Editor.modes.css,
|
||||||
readOnly: this._state.readOnly,
|
readOnly: this._state.readOnly,
|
||||||
keys: this._getKeyBindings()
|
autoCloseBrackets: "{}()[]",
|
||||||
|
extraKeys: this._getKeyBindings()
|
||||||
};
|
};
|
||||||
|
let sourceEditor = new Editor(config);
|
||||||
|
|
||||||
sourceEditor.init(inputElement, config, function onSourceEditorReady() {
|
sourceEditor.appendTo(inputElement).then(() => {
|
||||||
setupBracketCompletion(sourceEditor);
|
sourceEditor.on("change", () => {
|
||||||
sourceEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
|
|
||||||
function onTextChanged(event) {
|
|
||||||
this.updateStyleSheet();
|
this.updateStyleSheet();
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
this._sourceEditor = sourceEditor;
|
this._sourceEditor = sourceEditor;
|
||||||
|
|
||||||
@@ -223,15 +230,14 @@ StyleSheetEditor.prototype = {
|
|||||||
sourceEditor.focus();
|
sourceEditor.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceEditor.setTopIndex(this._state.topIndex);
|
sourceEditor.setFirstVisibleLine(this._state.topIndex);
|
||||||
sourceEditor.setSelection(this._state.selection.start,
|
sourceEditor.setSelection(this._state.selection.start,
|
||||||
this._state.selection.end);
|
this._state.selection.end);
|
||||||
|
|
||||||
this.emit("source-editor-load");
|
this.emit("source-editor-load");
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
sourceEditor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
|
sourceEditor.on("change", this._onPropertyChange);
|
||||||
this._onPropertyChange);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,7 +252,7 @@ StyleSheetEditor.prototype = {
|
|||||||
if (this.sourceEditor) {
|
if (this.sourceEditor) {
|
||||||
return promise.resolve(this);
|
return promise.resolve(this);
|
||||||
}
|
}
|
||||||
this.on("source-editor-load", (event) => {
|
this.on("source-editor-load", () => {
|
||||||
deferred.resolve(this);
|
deferred.resolve(this);
|
||||||
});
|
});
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@@ -268,7 +274,7 @@ StyleSheetEditor.prototype = {
|
|||||||
*/
|
*/
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
if (this._sourceEditor) {
|
if (this._sourceEditor) {
|
||||||
this._sourceEditor.setTopIndex(this._state.topIndex);
|
this._sourceEditor.setFirstVisibleLine(this._state.topIndex);
|
||||||
}
|
}
|
||||||
this.focus();
|
this.focus();
|
||||||
},
|
},
|
||||||
@@ -370,7 +376,7 @@ StyleSheetEditor.prototype = {
|
|||||||
if (callback) {
|
if (callback) {
|
||||||
callback(returnFile);
|
callback(returnFile);
|
||||||
}
|
}
|
||||||
this.sourceEditor.dirty = false;
|
this.sourceEditor.markClean();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -384,28 +390,15 @@ StyleSheetEditor.prototype = {
|
|||||||
* @return {array} key binding objects for the source editor
|
* @return {array} key binding objects for the source editor
|
||||||
*/
|
*/
|
||||||
_getKeyBindings: function() {
|
_getKeyBindings: function() {
|
||||||
let bindings = [];
|
let bindings = {};
|
||||||
|
|
||||||
bindings.push({
|
bindings[ctrl(_("saveStyleSheet.commandkey"))] = () => {
|
||||||
action: "StyleEditor.save",
|
|
||||||
code: _("saveStyleSheet.commandkey"),
|
|
||||||
accel: true,
|
|
||||||
callback: function save() {
|
|
||||||
this.saveToFile(this.savedFile);
|
this.saveToFile(this.savedFile);
|
||||||
return true;
|
};
|
||||||
}.bind(this)
|
|
||||||
});
|
|
||||||
|
|
||||||
bindings.push({
|
bindings["Shift-" + ctrl(_("saveStyleSheet.commandkey"))] = () => {
|
||||||
action: "StyleEditor.saveAs",
|
|
||||||
code: _("saveStyleSheet.commandkey"),
|
|
||||||
accel: true,
|
|
||||||
shift: true,
|
|
||||||
callback: function saveAs() {
|
|
||||||
this.saveToFile();
|
this.saveToFile();
|
||||||
return true;
|
};
|
||||||
}.bind(this)
|
|
||||||
});
|
|
||||||
|
|
||||||
return bindings;
|
return bindings;
|
||||||
},
|
},
|
||||||
@@ -426,18 +419,6 @@ const TAB_CHARS = "\t";
|
|||||||
const OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
const OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
||||||
const LINE_SEPARATOR = OS === "WINNT" ? "\r\n" : "\n";
|
const LINE_SEPARATOR = OS === "WINNT" ? "\r\n" : "\n";
|
||||||
|
|
||||||
/**
|
|
||||||
* Return string that repeats text for aCount times.
|
|
||||||
*
|
|
||||||
* @param string text
|
|
||||||
* @param number aCount
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function repeat(text, aCount)
|
|
||||||
{
|
|
||||||
return (new Array(aCount + 1)).join(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prettify minified CSS text.
|
* Prettify minified CSS text.
|
||||||
* This prettifies CSS code where there is no indentation in usual places while
|
* This prettifies CSS code where there is no indentation in usual places while
|
||||||
@@ -469,7 +450,7 @@ function prettifyCSS(text)
|
|||||||
parts.push(indent + text.substring(partStart, i));
|
parts.push(indent + text.substring(partStart, i));
|
||||||
partStart = i;
|
partStart = i;
|
||||||
}
|
}
|
||||||
indent = repeat(TAB_CHARS, --indentLevel);
|
indent = TAB_CHARS.repeat(--indentLevel);
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
case ";":
|
case ";":
|
||||||
case "{":
|
case "{":
|
||||||
@@ -493,58 +474,9 @@ function prettifyCSS(text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c == "{") {
|
if (c == "{") {
|
||||||
indent = repeat(TAB_CHARS, ++indentLevel);
|
indent = TAB_CHARS.repeat(++indentLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return parts.join(LINE_SEPARATOR);
|
return parts.join(LINE_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up bracket completion on a given SourceEditor.
|
|
||||||
* This automatically closes the following CSS brackets: "{", "(", "["
|
|
||||||
*
|
|
||||||
* @param SourceEditor sourceEditor
|
|
||||||
*/
|
|
||||||
function setupBracketCompletion(sourceEditor)
|
|
||||||
{
|
|
||||||
let editorElement = sourceEditor.editorElement;
|
|
||||||
let pairs = {
|
|
||||||
123: { // {
|
|
||||||
closeString: "}",
|
|
||||||
closeKeyCode: Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET
|
|
||||||
},
|
|
||||||
40: { // (
|
|
||||||
closeString: ")",
|
|
||||||
closeKeyCode: Ci.nsIDOMKeyEvent.DOM_VK_0
|
|
||||||
},
|
|
||||||
91: { // [
|
|
||||||
closeString: "]",
|
|
||||||
closeKeyCode: Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
editorElement.addEventListener("keypress", function onKeyPress(event) {
|
|
||||||
let pair = pairs[event.charCode];
|
|
||||||
if (!pair || event.ctrlKey || event.metaKey ||
|
|
||||||
event.accelKey || event.altKey) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We detected an open bracket, sending closing character
|
|
||||||
let keyCode = pair.closeKeyCode;
|
|
||||||
let charCode = pair.closeString.charCodeAt(0);
|
|
||||||
let modifiers = 0;
|
|
||||||
let utils = editorElement.ownerDocument.defaultView.
|
|
||||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
|
||||||
getInterface(Ci.nsIDOMWindowUtils);
|
|
||||||
|
|
||||||
if (utils.sendKeyEvent("keydown", keyCode, 0, modifiers)) {
|
|
||||||
utils.sendKeyEvent("keypress", 0, charCode, modifiers);
|
|
||||||
}
|
|
||||||
utils.sendKeyEvent("keyup", keyCode, 0, modifiers);
|
|
||||||
// and rewind caret
|
|
||||||
sourceEditor.setCaretOffset(sourceEditor.getCaretOffset() - 1);
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,21 +96,10 @@ function testEditor(aEditor) {
|
|||||||
is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
|
is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
|
||||||
"content's background color is initially white");
|
"content's background color is initially white");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("[", {accelKey: true}, gPanelWindow);
|
|
||||||
is(aEditor.sourceEditor.getText(), "",
|
|
||||||
"Nothing happened as it is a known shortcut in source editor");
|
|
||||||
|
|
||||||
EventUtils.synthesizeKey("]", {accelKey: true}, gPanelWindow);
|
|
||||||
is(aEditor.sourceEditor.getText(), "",
|
|
||||||
"Nothing happened as it is a known shortcut in source editor");
|
|
||||||
|
|
||||||
for each (let c in TESTCASE_CSS_SOURCE) {
|
for each (let c in TESTCASE_CSS_SOURCE) {
|
||||||
EventUtils.synthesizeKey(c, {}, gPanelWindow);
|
EventUtils.synthesizeKey(c, {}, gPanelWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
|
||||||
"rule bracket has been auto-closed");
|
|
||||||
|
|
||||||
ok(aEditor.unsaved,
|
ok(aEditor.unsaved,
|
||||||
"new editor has unsaved flag");
|
"new editor has unsaved flag");
|
||||||
|
|
||||||
@@ -122,6 +111,9 @@ function testEditor(aEditor) {
|
|||||||
function onTransitionEnd() {
|
function onTransitionEnd() {
|
||||||
content.removeEventListener("transitionend", onTransitionEnd, false);
|
content.removeEventListener("transitionend", onTransitionEnd, false);
|
||||||
|
|
||||||
|
is(gNewEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
||||||
|
"rule bracket has been auto-closed");
|
||||||
|
|
||||||
let computedStyle = content.getComputedStyle(content.document.body, null);
|
let computedStyle = content.getComputedStyle(content.document.body, null);
|
||||||
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
|
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
|
||||||
"content's background color has been updated to red");
|
"content's background color has been updated to red");
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ function testRemembered()
|
|||||||
{
|
{
|
||||||
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
|
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
|
||||||
|
|
||||||
let {line, col} = gUI.selectedEditor.sourceEditor.getCaretPosition();
|
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||||
is(line, LINE_NO, "correct line selected");
|
is(line, LINE_NO, "correct line selected");
|
||||||
is(col, COL_NO, "correct column selected");
|
is(ch, COL_NO, "correct column selected");
|
||||||
|
|
||||||
testNewPage();
|
testNewPage();
|
||||||
}
|
}
|
||||||
@@ -80,9 +80,9 @@ function testNotRemembered()
|
|||||||
{
|
{
|
||||||
is(gUI.selectedEditor, gUI.editors[0], "first editor is selected");
|
is(gUI.selectedEditor, gUI.editors[0], "first editor is selected");
|
||||||
|
|
||||||
let {line, col} = gUI.selectedEditor.sourceEditor.getCaretPosition();
|
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||||
is(line, 0, "first line is selected");
|
is(line, 0, "first line is selected");
|
||||||
is(col, 0, "first column is selected");
|
is(ch, 0, "first column is selected");
|
||||||
|
|
||||||
gUI = null;
|
gUI = null;
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ function runTests(aUI)
|
|||||||
is(aUI.editors.length, 2,
|
is(aUI.editors.length, 2,
|
||||||
"there is 2 stylesheets initially");
|
"there is 2 stylesheets initially");
|
||||||
|
|
||||||
aUI.editors[0].getSourceEditor().then(function onEditorAttached(aEditor) {
|
aUI.editors[0].getSourceEditor().then(aEditor => {
|
||||||
executeSoon(function () {
|
executeSoon(function () {
|
||||||
waitForFocus(function () {
|
waitForFocus(function () {
|
||||||
// queue a resize to inverse aspect ratio
|
// queue a resize to inverse aspect ratio
|
||||||
// this will trigger a detach and reattach (to workaround bug 254144)
|
// this will trigger a detach and reattach (to workaround bug 254144)
|
||||||
let originalSourceEditor = aEditor.sourceEditor;
|
let originalSourceEditor = aEditor.sourceEditor;
|
||||||
aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved
|
let editor = aEditor.sourceEditor;
|
||||||
|
editor.setCursor(editor.getPosition(4)); // to check the caret is preserved
|
||||||
|
|
||||||
gOriginalWidth = gPanelWindow.outerWidth;
|
gOriginalWidth = gPanelWindow.outerWidth;
|
||||||
gOriginalHeight = gPanelWindow.outerHeight;
|
gOriginalHeight = gPanelWindow.outerHeight;
|
||||||
@@ -44,7 +45,8 @@ function runTests(aUI)
|
|||||||
executeSoon(function () {
|
executeSoon(function () {
|
||||||
is(aEditor.sourceEditor, originalSourceEditor,
|
is(aEditor.sourceEditor, originalSourceEditor,
|
||||||
"the editor still references the same SourceEditor instance");
|
"the editor still references the same SourceEditor instance");
|
||||||
is(aEditor.sourceEditor.getCaretOffset(), 4,
|
let editor = aEditor.sourceEditor;
|
||||||
|
is(editor.getOffset(editor.getCursor()), 4,
|
||||||
"the caret position has been preserved");
|
"the caret position has been preserved");
|
||||||
|
|
||||||
// queue a resize to original aspect ratio
|
// queue a resize to original aspect ratio
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ function performLineCheck(aEditor, aLine, aCallback)
|
|||||||
{
|
{
|
||||||
function checkForCorrectState()
|
function checkForCorrectState()
|
||||||
{
|
{
|
||||||
is(aEditor.sourceEditor.getCaretPosition().line, aLine,
|
is(aEditor.sourceEditor.getCursor().line, aLine,
|
||||||
"correct line is selected");
|
"correct line is selected");
|
||||||
is(StyleEditorUI.selectedStyleSheetIndex, aEditor.styleSheet.styleSheetIndex,
|
is(StyleEditorUI.selectedStyleSheetIndex, aEditor.styleSheet.styleSheetIndex,
|
||||||
"correct stylesheet is selected in the editor");
|
"correct stylesheet is selected in the editor");
|
||||||
|
|||||||
Reference in New Issue
Block a user