mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 919978 - Make StyleEditor use CodeMirror, r=anton, msucan
This commit is contained in:
parent
f8ceba11c2
commit
d23a69252d
@ -862,7 +862,7 @@ FilterView.prototype = {
|
||||
_performLineSearch: function(aLine) {
|
||||
// Make sure we're actually searching for a valid line.
|
||||
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, {
|
||||
charOffset: scriptOffset,
|
||||
columnOffset: actualLocation.start.column,
|
||||
align: "center",
|
||||
noDebug: true
|
||||
});
|
||||
}
|
||||
|
@ -457,7 +457,8 @@ let DebuggerView = {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -37,6 +37,7 @@ browser.jar:
|
||||
content/browser/devtools/codemirror/htmlmixed.js (sourceeditor/codemirror/htmlmixed.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/closebrackets.js (sourceeditor/codemirror/closebrackets.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/search.js (sourceeditor/codemirror/search/search.js)
|
||||
|
@ -40,6 +40,7 @@ in the LICENSE file:
|
||||
* dialog/dialog.js
|
||||
* javascript.js
|
||||
* matchbrackets.js
|
||||
* closebrackets.js
|
||||
* search/match-highlighter.js
|
||||
* search/search.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
|
||||
* result.
|
||||
*/
|
||||
function doSearch(cm, rev, query) {
|
||||
function doSearch(ctx, rev, query) {
|
||||
let { cm } = ctx;
|
||||
let state = getSearchState(cm);
|
||||
|
||||
if (state.query)
|
||||
return searchNext(cm, rev);
|
||||
return searchNext(ctx, rev);
|
||||
|
||||
cm.operation(function () {
|
||||
if (state.query) return;
|
||||
|
||||
state.query = query;
|
||||
state.posFrom = state.posTo = { line: 0, ch: 0 };
|
||||
searchNext(cm, rev);
|
||||
searchNext(ctx, rev);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the next result of a saved search.
|
||||
*/
|
||||
function searchNext(cm, rev) {
|
||||
function searchNext(ctx, rev) {
|
||||
let { cm, ed } = ctx;
|
||||
cm.operation(function () {
|
||||
let state = getSearchState(cm)
|
||||
let cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
||||
@ -92,6 +94,7 @@ function searchNext(cm, rev) {
|
||||
return;
|
||||
}
|
||||
|
||||
ed.alignLine(cursor.from().line, "center");
|
||||
cm.setSelection(cursor.from(), cursor.to());
|
||||
state.posFrom = cursor.from();
|
||||
state.posTo = cursor.to();
|
||||
@ -236,25 +239,22 @@ function clearDebugLocation(ctx) {
|
||||
* Starts a new search.
|
||||
*/
|
||||
function find(ctx, query) {
|
||||
let { cm } = ctx;
|
||||
clearSearch(cm);
|
||||
doSearch(cm, false, query);
|
||||
clearSearch(ctx.cm);
|
||||
doSearch(ctx, false, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the next item based on the currently saved search.
|
||||
*/
|
||||
function findNext(ctx, query) {
|
||||
let { cm } = ctx;
|
||||
doSearch(cm, false, query);
|
||||
doSearch(ctx, false, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the previous item based on the currently saved search.
|
||||
*/
|
||||
function findPrev(ctx, query) {
|
||||
let { cm } = ctx;
|
||||
doSearch(cm, true, query);
|
||||
doSearch(ctx, true, query);
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,6 +12,10 @@ const EXPAND_TAB = "devtools.editor.expandtab";
|
||||
const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
|
||||
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 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/search.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/javascript.js",
|
||||
"chrome://browser/content/devtools/codemirror/xml.js",
|
||||
@ -59,7 +64,6 @@ const CM_IFRAME =
|
||||
const CM_MAPPING = [
|
||||
"focus",
|
||||
"hasFocus",
|
||||
"setCursor",
|
||||
"getCursor",
|
||||
"somethingSelected",
|
||||
"setSelection",
|
||||
@ -78,6 +82,8 @@ const CM_JUMP_DIALOG = [
|
||||
+ " <input type=text style='width: 10em'/>"
|
||||
];
|
||||
|
||||
const { cssProperties, cssValues, cssColors } = getCSSKeywords();
|
||||
|
||||
const editors = new WeakMap();
|
||||
|
||||
Editor.modes = {
|
||||
@ -192,7 +198,21 @@ Editor.prototype = {
|
||||
CM_SCRIPTS.forEach((url) =>
|
||||
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
|
||||
// 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 () {
|
||||
this.container = 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
|
||||
* editor-specific commands such as find, jump to line,
|
||||
|
@ -73,7 +73,7 @@ StyleEditorUI.prototype = {
|
||||
return true;
|
||||
}
|
||||
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
|
||||
if (this.selectedEditor && this.selectedEditor.sourceEditor) {
|
||||
let href = this.selectedEditor.styleSheet.href;
|
||||
let {line, col} = this.selectedEditor.sourceEditor.getCaretPosition();
|
||||
this.selectStyleSheet(href, line, col);
|
||||
let {line, ch} = this.selectedEditor.sourceEditor.getCursor();
|
||||
this.selectStyleSheet(href, line, ch);
|
||||
}
|
||||
|
||||
this._clearStyleSheetEditors();
|
||||
@ -365,7 +365,7 @@ StyleEditorUI.prototype = {
|
||||
col = col || 0;
|
||||
|
||||
editor.getSourceEditor().then(() => {
|
||||
editor.sourceEditor.setCaretPosition(line, col);
|
||||
editor.sourceEditor.setCursor({line: line, ch: col});
|
||||
});
|
||||
|
||||
this._view.activeSummary = editor.summary;
|
||||
|
@ -11,21 +11,26 @@ const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
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/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
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");
|
||||
|
||||
|
||||
const SAVE_ERROR = "error-save";
|
||||
|
||||
// max update frequency in ms (avoid potential typing lag and/or flicker)
|
||||
// @see StyleEditor.updateStylesheet
|
||||
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
|
||||
* object.
|
||||
@ -58,7 +63,10 @@ function StyleSheetEditor(styleSheet, win, file, isNew) {
|
||||
|
||||
this._state = { // state to use when inputElement attaches
|
||||
text: "",
|
||||
selection: {start: 0, end: 0},
|
||||
selection: {
|
||||
start: {line: 0, ch: 0},
|
||||
end: {line: 0, ch: 0}
|
||||
},
|
||||
readOnly: false,
|
||||
topIndex: 0, // the first visible line
|
||||
};
|
||||
@ -92,7 +100,7 @@ StyleSheetEditor.prototype = {
|
||||
* Whether there are unsaved changes in the editor
|
||||
*/
|
||||
get unsaved() {
|
||||
return this._sourceEditor && this._sourceEditor.dirty;
|
||||
return this._sourceEditor && !this._sourceEditor.isClean();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -200,21 +208,20 @@ StyleSheetEditor.prototype = {
|
||||
load: function(inputElement) {
|
||||
this._inputElement = inputElement;
|
||||
|
||||
let sourceEditor = new SourceEditor();
|
||||
let config = {
|
||||
initialText: this._state.text,
|
||||
showLineNumbers: true,
|
||||
mode: SourceEditor.MODES.CSS,
|
||||
value: this._state.text,
|
||||
lineNumbers: true,
|
||||
mode: Editor.modes.css,
|
||||
readOnly: this._state.readOnly,
|
||||
keys: this._getKeyBindings()
|
||||
autoCloseBrackets: "{}()[]",
|
||||
extraKeys: this._getKeyBindings()
|
||||
};
|
||||
let sourceEditor = new Editor(config);
|
||||
|
||||
sourceEditor.init(inputElement, config, function onSourceEditorReady() {
|
||||
setupBracketCompletion(sourceEditor);
|
||||
sourceEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
|
||||
function onTextChanged(event) {
|
||||
sourceEditor.appendTo(inputElement).then(() => {
|
||||
sourceEditor.on("change", () => {
|
||||
this.updateStyleSheet();
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
this._sourceEditor = sourceEditor;
|
||||
|
||||
@ -223,15 +230,14 @@ StyleSheetEditor.prototype = {
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
sourceEditor.setTopIndex(this._state.topIndex);
|
||||
sourceEditor.setFirstVisibleLine(this._state.topIndex);
|
||||
sourceEditor.setSelection(this._state.selection.start,
|
||||
this._state.selection.end);
|
||||
|
||||
this.emit("source-editor-load");
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
sourceEditor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
|
||||
this._onPropertyChange);
|
||||
sourceEditor.on("change", this._onPropertyChange);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -246,7 +252,7 @@ StyleSheetEditor.prototype = {
|
||||
if (this.sourceEditor) {
|
||||
return promise.resolve(this);
|
||||
}
|
||||
this.on("source-editor-load", (event) => {
|
||||
this.on("source-editor-load", () => {
|
||||
deferred.resolve(this);
|
||||
});
|
||||
return deferred.promise;
|
||||
@ -268,7 +274,7 @@ StyleSheetEditor.prototype = {
|
||||
*/
|
||||
onShow: function() {
|
||||
if (this._sourceEditor) {
|
||||
this._sourceEditor.setTopIndex(this._state.topIndex);
|
||||
this._sourceEditor.setFirstVisibleLine(this._state.topIndex);
|
||||
}
|
||||
this.focus();
|
||||
},
|
||||
@ -370,7 +376,7 @@ StyleSheetEditor.prototype = {
|
||||
if (callback) {
|
||||
callback(returnFile);
|
||||
}
|
||||
this.sourceEditor.dirty = false;
|
||||
this.sourceEditor.markClean();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
@ -384,28 +390,15 @@ StyleSheetEditor.prototype = {
|
||||
* @return {array} key binding objects for the source editor
|
||||
*/
|
||||
_getKeyBindings: function() {
|
||||
let bindings = [];
|
||||
let bindings = {};
|
||||
|
||||
bindings.push({
|
||||
action: "StyleEditor.save",
|
||||
code: _("saveStyleSheet.commandkey"),
|
||||
accel: true,
|
||||
callback: function save() {
|
||||
bindings[ctrl(_("saveStyleSheet.commandkey"))] = () => {
|
||||
this.saveToFile(this.savedFile);
|
||||
return true;
|
||||
}.bind(this)
|
||||
});
|
||||
};
|
||||
|
||||
bindings.push({
|
||||
action: "StyleEditor.saveAs",
|
||||
code: _("saveStyleSheet.commandkey"),
|
||||
accel: true,
|
||||
shift: true,
|
||||
callback: function saveAs() {
|
||||
bindings["Shift-" + ctrl(_("saveStyleSheet.commandkey"))] = () => {
|
||||
this.saveToFile();
|
||||
return true;
|
||||
}.bind(this)
|
||||
});
|
||||
};
|
||||
|
||||
return bindings;
|
||||
},
|
||||
@ -426,18 +419,6 @@ const TAB_CHARS = "\t";
|
||||
const OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
||||
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.
|
||||
* 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));
|
||||
partStart = i;
|
||||
}
|
||||
indent = repeat(TAB_CHARS, --indentLevel);
|
||||
indent = TAB_CHARS.repeat(--indentLevel);
|
||||
/* fallthrough */
|
||||
case ";":
|
||||
case "{":
|
||||
@ -493,58 +474,9 @@ function prettifyCSS(text)
|
||||
}
|
||||
|
||||
if (c == "{") {
|
||||
indent = repeat(TAB_CHARS, ++indentLevel);
|
||||
indent = TAB_CHARS.repeat(++indentLevel);
|
||||
}
|
||||
}
|
||||
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)",
|
||||
"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) {
|
||||
EventUtils.synthesizeKey(c, {}, gPanelWindow);
|
||||
}
|
||||
|
||||
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
||||
"rule bracket has been auto-closed");
|
||||
|
||||
ok(aEditor.unsaved,
|
||||
"new editor has unsaved flag");
|
||||
|
||||
@ -122,6 +111,9 @@ function testEditor(aEditor) {
|
||||
function onTransitionEnd() {
|
||||
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);
|
||||
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
|
||||
"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");
|
||||
|
||||
let {line, col} = gUI.selectedEditor.sourceEditor.getCaretPosition();
|
||||
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(col, COL_NO, "correct column selected");
|
||||
is(ch, COL_NO, "correct column selected");
|
||||
|
||||
testNewPage();
|
||||
}
|
||||
@ -80,9 +80,9 @@ function testNotRemembered()
|
||||
{
|
||||
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(col, 0, "first column is selected");
|
||||
is(ch, 0, "first column is selected");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
|
@ -29,13 +29,14 @@ function runTests(aUI)
|
||||
is(aUI.editors.length, 2,
|
||||
"there is 2 stylesheets initially");
|
||||
|
||||
aUI.editors[0].getSourceEditor().then(function onEditorAttached(aEditor) {
|
||||
aUI.editors[0].getSourceEditor().then(aEditor => {
|
||||
executeSoon(function () {
|
||||
waitForFocus(function () {
|
||||
// queue a resize to inverse aspect ratio
|
||||
// this will trigger a detach and reattach (to workaround bug 254144)
|
||||
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;
|
||||
gOriginalHeight = gPanelWindow.outerHeight;
|
||||
@ -44,7 +45,8 @@ function runTests(aUI)
|
||||
executeSoon(function () {
|
||||
is(aEditor.sourceEditor, originalSourceEditor,
|
||||
"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");
|
||||
|
||||
// queue a resize to original aspect ratio
|
||||
|
@ -131,7 +131,7 @@ function performLineCheck(aEditor, aLine, aCallback)
|
||||
{
|
||||
function checkForCorrectState()
|
||||
{
|
||||
is(aEditor.sourceEditor.getCaretPosition().line, aLine,
|
||||
is(aEditor.sourceEditor.getCursor().line, aLine,
|
||||
"correct line is selected");
|
||||
is(StyleEditorUI.selectedStyleSheetIndex, aEditor.styleSheet.styleSheetIndex,
|
||||
"correct stylesheet is selected in the editor");
|
||||
|
Loading…
Reference in New Issue
Block a user