Bug 983473 - Put a CodeMirror instance in JsTerm; r=bgrins.

This is only about adding an editor in the JsTerm and making
sure we can still execute input strings.
The styles should stay the same, except that now we don't have
to do the computation for the input height, since they're already
done in CodeMirror. In-line style, history navigation and
autocompletion will be handled in separate bugs.
The creation of the editor might be done outside of the JsTerm in
the future so we can re-use it to syntax highlight Evaluation input
in the output; but not in this bug since it would need to move
jsterm.execute as well.

MozReview-Commit-ID: 75TmF055mkp

--HG--
extra : rebase_source : ec7edb17ffb13c757ed51e03a1174399ea2bcbde
This commit is contained in:
Nicolas Chevobbe 2018-05-21 10:38:47 +02:00
Родитель 47ca9cafec
Коммит 6a3954e6e8
8 изменённых файлов: 147 добавлений и 27 удалений

Просмотреть файл

@ -234,7 +234,7 @@ Editor.prototype = {
/**
* Appends the current Editor instance to the element specified by
* 'el'. You can also provide your won iframe to host the editor as
* 'el'. You can also provide your own iframe to host the editor as
* an optional second parameter. This method actually creates and
* loads CodeMirror and all its dependencies.
*

Просмотреть файл

@ -348,6 +348,40 @@ textarea.jsterm-input-node:focus {
border-bottom-right-radius: 0;
}
/* CodeMirror-powered JsTerm */
.jsterm-cm .jsterm-input-container {
/* Always allow scrolling on input - it auto expands in js by setting height,
but don't want it to get bigger than the window. 24px = toolbar height. */
max-height: calc(90vh - 24px);
}
.jsterm-cm .jsterm-input-container > .CodeMirror {
border: 1px solid transparent;
font-size: inherit;
line-height: 16px;
padding-inline-start: 20px;
/* input icon */
background-image: var(--theme-command-line-image);
background-repeat: no-repeat;
background-size: 16px 16px;
background-position: 4px 4px;
}
.jsterm-cm .jsterm-input-container > .CodeMirror-focused {
background-image: var(--theme-command-line-image-focus);
border: 1px solid var(--blue-50);
transition: border-color 0.2s ease-in-out;
}
:root[platform="mac"] .jsterm-cm .jsterm-input-container > .CodeMirror {
border-radius: 0 0 4px 4px;
}
/* Unset the bottom right radius on the jsterm inputs when the sidebar is visible */
:root[platform="mac"] .jsterm-cm .sidebar ~ .jsterm-input-container > .CodeMirror {
border-bottom-right-radius: 0;
}
/* Security styles */
.message.security > .indent {
@ -630,7 +664,6 @@ a.learn-more-link.webconsole-learn-more-link {
}
.webconsole-output {
flex: 1;
overflow: auto;
}
@ -955,6 +988,7 @@ body #output-container {
grid-template-columns: minmax(200px, 1fr) auto;
grid-template-rows: auto 1fr auto auto;
height: 100%;
max-height: 100%;
width: 100vw;
}
@ -1035,6 +1069,13 @@ html[dir="rtl"] .webconsole-output-wrapper img.collapse-button.arrow:not(.expand
grid-row: 1 / -1;
grid-column: -1 / -2;
background-color: var(--theme-sidebar-background);
border-inline-start: 1px solid var(--theme-splitter-color);
}
.sidebar .splitter {
/* Let the parent component handle the border. This is needed otherwise there is a visual
glitch between the input and the sidebar borders */
background-color: transparent;
}
.split-box.vert.sidebar {

Просмотреть файл

@ -43,6 +43,7 @@ class App extends Component {
onFirstMeaningfulPaint: PropTypes.func.isRequired,
serviceContainer: PropTypes.object.isRequired,
closeSplitConsole: PropTypes.func.isRequired,
jstermCodeMirror: PropTypes.bool,
};
}
@ -122,8 +123,14 @@ class App extends Component {
onFirstMeaningfulPaint,
serviceContainer,
closeSplitConsole,
jstermCodeMirror,
} = this.props;
const classNames = ["webconsole-output-wrapper"];
if (jstermCodeMirror) {
classNames.push("jsterm-cm");
}
// Render the entire Console panel. The panel consists
// from the following parts:
// * FilterBar - Buttons & free text for content filtering
@ -133,7 +140,7 @@ class App extends Component {
// * JSTerm - Input command line.
return (
div({
className: "webconsole-output-wrapper",
className: classNames.join(" "),
ref: node => {
this.node = node;
}},
@ -158,6 +165,7 @@ class App extends Component {
JSTerm({
hud,
onPaste: this.onPaste,
codeMirrorEnabled: jstermCodeMirror,
}),
)
);

Просмотреть файл

@ -18,6 +18,7 @@ loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
const l10n = require("devtools/client/webconsole/webconsole-l10n");
@ -53,6 +54,7 @@ class JSTerm extends Component {
hud: PropTypes.object.isRequired,
// Handler for clipboard 'paste' event (also used for 'drop' event).
onPaste: PropTypes.func,
codeMirrorEnabled: PropTypes.bool,
};
}
@ -146,10 +148,6 @@ class JSTerm extends Component {
}
componentDidMount() {
if (!this.inputNode) {
return;
}
let autocompleteOptions = {
onSelect: this.onAutocompleteSelect.bind(this),
onClick: this.acceptProposedCompletion.bind(this),
@ -166,21 +164,54 @@ class JSTerm extends Component {
// such as the browser console which doesn't have a toolbox.
this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
this.inputBorderSize = this.inputNode.getBoundingClientRect().height -
this.inputNode.clientHeight;
this.inputBorderSize = this.inputNode
? this.inputNode.getBoundingClientRect().height - this.inputNode.clientHeight
: 0;
// Update the character width and height needed for the popup offset
// calculations.
this._updateCharSize();
this.inputNode.addEventListener("keypress", this._keyPress);
this.inputNode.addEventListener("input", this._inputEventHandler);
this.inputNode.addEventListener("keyup", this._inputEventHandler);
this.inputNode.addEventListener("focus", this._focusEventHandler);
if (this.props.codeMirrorEnabled) {
if (this.node) {
this.editor = new Editor({
autofocus: true,
enableCodeFolding: false,
gutters: [],
lineWrapping: true,
mode: Editor.modes.js,
styleActiveLine: false,
tabIndex: "0",
viewportMargin: Infinity,
extraKeys: {
"Enter": (e, cm) => {
let autoMultiline = Services.prefs.getBoolPref(PREF_AUTO_MULTILINE);
if (e.shiftKey
|| (
!Debugger.isCompilableUnit(this.getInputValue())
&& autoMultiline
)
) {
// shift return or incomplete statement
return "CodeMirror.Pass";
}
this.execute();
return null;
},
},
});
this.editor.appendToLocalElement(this.node);
}
} else if (this.inputNode) {
this.inputNode.addEventListener("keypress", this._keyPress);
this.inputNode.addEventListener("input", this._inputEventHandler);
this.inputNode.addEventListener("keyup", this._inputEventHandler);
this.inputNode.addEventListener("focus", this._focusEventHandler);
this.focus();
}
this.hud.window.addEventListener("blur", this._blurEventHandler);
this.lastInputValue && this.setInputValue(this.lastInputValue);
this.focus();
}
shouldComponentUpdate() {
@ -256,7 +287,9 @@ class JSTerm extends Component {
}
focus() {
if (this.inputNode && !this.inputNode.getAttribute("focused")) {
if (this.editor) {
this.editor.focus();
} else if (this.inputNode && !this.inputNode.getAttribute("focused")) {
this.inputNode.focus();
}
}
@ -531,6 +564,10 @@ class JSTerm extends Component {
* @returns void
*/
resizeInput() {
if (this.props.codeMirrorEnabled) {
return;
}
if (!this.inputNode) {
return;
}
@ -542,10 +579,7 @@ class JSTerm extends Component {
inputNode.style.height = "auto";
// Now resize the input field to fit its contents.
// TODO: remove `inputNode.inputField.scrollHeight` when the old
// console UI is removed. See bug 1381834
let scrollHeight = inputNode.inputField ?
inputNode.inputField.scrollHeight : inputNode.scrollHeight;
let scrollHeight = inputNode.scrollHeight;
if (scrollHeight > 0) {
inputNode.style.height = (scrollHeight + this.inputBorderSize) + "px";
@ -562,13 +596,20 @@ class JSTerm extends Component {
* @returns void
*/
setInputValue(newValue) {
if (!this.inputNode) {
return;
if (this.props.codeMirrorEnabled) {
if (this.editor) {
this.editor.setText(newValue);
}
} else {
if (!this.inputNode) {
return;
}
this.inputNode.value = newValue;
this.completeNode.value = "";
}
this.inputNode.value = newValue;
this.lastInputValue = newValue;
this.completeNode.value = "";
this.resizeInput();
this._inputChanged = true;
this.emit("set-input-value");
@ -579,6 +620,10 @@ class JSTerm extends Component {
* @returns string
*/
getInputValue() {
if (this.props.codeMirrorEnabled) {
return this.editor.getText() || "";
}
return this.inputNode ? this.inputNode.value || "" : "";
}
@ -1256,6 +1301,10 @@ class JSTerm extends Component {
* @private
*/
_updateCharSize() {
if (this.props.codeMirrorEnabled || !this.inputNode) {
return;
}
let doc = this.hud.document;
let tempLabel = doc.createElement("span");
let style = tempLabel.style;
@ -1307,6 +1356,18 @@ class JSTerm extends Component {
return null;
}
if (this.props.codeMirrorEnabled) {
return dom.div({
className: "jsterm-input-container devtools-monospace",
key: "jsterm-container",
style: {direction: "ltr"},
"aria-live": "off",
ref: node => {
this.node = node;
},
});
}
let {
onPaste
} = this.props;

Просмотреть файл

@ -52,8 +52,11 @@ const prefs = {
FILTER_BAR: "ui.filterbar",
// Persist is only used by the webconsole.
PERSIST: "devtools.webconsole.persistlog",
},
FEATURES: {
// We use the same pref to enable the sidebar on webconsole and browser console.
SIDEBAR_TOGGLE: "devtools.webconsole.sidebarToggle",
JSTERM_CODE_MIRROR: "devtools.webconsole.jsterm.codeMirror",
}
}
};

Просмотреть файл

@ -7,7 +7,8 @@
const PrefState = (overrides) => Object.freeze(Object.assign({
logLimit: 1000,
sidebarToggle: false
sidebarToggle: false,
jstermCodeMirror: false,
}, overrides));
function prefs(state = PrefState(), action) {

Просмотреть файл

@ -49,10 +49,15 @@ function configureStore(hud, options = {}) {
const logLimit = options.logLimit
|| Math.max(getIntPref("devtools.hud.loglimit"), 1);
const sidebarToggle = getBoolPref(PREFS.UI.SIDEBAR_TOGGLE);
const sidebarToggle = getBoolPref(PREFS.FEATURES.SIDEBAR_TOGGLE);
const jstermCodeMirror = getBoolPref(PREFS.FEATURES.JSTERM_CODE_MIRROR);
const initialState = {
prefs: PrefState({ logLimit, sidebarToggle }),
prefs: PrefState({
logLimit,
sidebarToggle,
jstermCodeMirror,
}),
filters: FilterState({
error: getBoolPref(PREFS.FILTER.ERROR),
warn: getBoolPref(PREFS.FILTER.WARN),

Просмотреть файл

@ -214,6 +214,7 @@ WebConsoleOutputWrapper.prototype = {
hud,
onFirstMeaningfulPaint: resolve,
closeSplitConsole: this.closeSplitConsole.bind(this),
jstermCodeMirror: store.getState().prefs.jstermCodeMirror,
});
// Render the root Application component.