Bug 1430558 - add closing parenthesis automatically in CSS autocompletes;r=gl

MozReview-Commit-ID: LLBrLC3Bq0t

--HG--
extra : rebase_source : c081b53042dcc14c6f7bb022b35edab12a5deff0
This commit is contained in:
Julian Descottes 2018-01-13 00:41:19 +01:00
parent 695dbf5db9
commit d06e921eb2
5 changed files with 145 additions and 16 deletions

View file

@ -83,10 +83,10 @@ const TEST_DATA_INNER = [
["u", "style=\"background:unset", 19, 23, true],
["r", "style=\"background:url", 20, 21, false],
["l", "style=\"background:url", 21, 21, false],
["(", "style=\"background:url(", 22, 22, false],
["'", "style=\"background:url('", 23, 23, false],
["1", "style=\"background:url('1", 24, 24, false],
["'", "style=\"background:url('1'", 25, 25, false],
["(", "style=\"background:url()", 22, 22, false],
["'", "style=\"background:url(')", 23, 23, false],
["1", "style=\"background:url('1)", 24, 24, false],
["'", "style=\"background:url('1')", 25, 25, false],
[")", "style=\"background:url('1')", 26, 26, false],
[";", "style=\"background:url('1');", 27, 27, false],
[" ", "style=\"background:url('1'); ", 28, 28, false],

View file

@ -26,6 +26,8 @@
const Services = require("Services");
const focusManager = Services.focus;
const {KeyCodes} = require("devtools/client/shared/keycodes");
const EventEmitter = require("devtools/shared/event-emitter");
const { findMostRelevantCssPropertyIndex } = require("./suggestion-picker");
loader.lazyRequireGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm", true);
@ -44,8 +46,10 @@ const MAX_POPUP_ENTRIES = 500;
const FOCUS_FORWARD = focusManager.MOVEFOCUS_FORWARD;
const FOCUS_BACKWARD = focusManager.MOVEFOCUS_BACKWARD;
const EventEmitter = require("devtools/shared/event-emitter");
const { findMostRelevantCssPropertyIndex } = require("./suggestion-picker");
const WORD_REGEXP = /\w/;
const isWordChar = function(str) {
return str && WORD_REGEXP.test(str);
};
/**
* Helper to check if the provided key matches one of the expected keys.
@ -1051,6 +1055,10 @@ InplaceEditor.prototype = {
let key = event.keyCode;
let input = this.input;
// We want to autoclose some characters, remember the pressed key in order to process
// it later on in maybeSuggestionCompletion().
this._pressedKey = event.key;
let multilineNavigation = !this._isSingleLine() &&
isKeyIn(key, "UP", "DOWN", "LEFT", "RIGHT");
let isPlainText = this.contentType == CONTENT_TYPES.PLAIN_TEXT;
@ -1293,6 +1301,7 @@ InplaceEditor.prototype = {
if (!this.input) {
return;
}
let preTimeoutQuery = this.input.value;
// Since we are calling this method from a keypress event handler, the
@ -1477,12 +1486,58 @@ InplaceEditor.prototype = {
} else {
this._hideAutocompletePopup();
}
this._autocloseParenthesis();
// This emit is mainly for the purpose of making the test flow simpler.
this.emit("after-suggest");
this._doValidation();
}, 0);
},
/**
* Automatically add closing parenthesis and skip closing parenthesis when needed.
*/
_autocloseParenthesis: function() {
// Split the current value at the cursor index to rebuild the string.
let parts = this._splitStringAt(this.input.value, this.input.selectionStart);
// Lookup the character following the caret to know if the string should be modified.
let nextChar = parts[1][0];
// Autocomplete closing parenthesis if the last key pressed was "(" and the next
// character is not a "word" character.
if (this._pressedKey == "(" && !isWordChar(nextChar)) {
this._updateValue(parts[0] + ")" + parts[1]);
}
// Skip inserting ")" if the next character is already a ")" (note that we actually
// insert and remove the extra ")" here, as the input has already been modified).
if (this._pressedKey == ")" && nextChar == ")") {
this._updateValue(parts[0] + parts[1].substring(1));
}
this._pressedKey = null;
},
/**
* Update the current value of the input while preserving the caret position.
*/
_updateValue: function(str) {
let start = this.input.selectionStart;
this.input.value = str;
this.input.setSelectionRange(start, start);
this._updateSize();
},
/**
* Split the provided string at the provided index. Returns an array of two strings.
* _splitStringAt("1234567", 3) will return ["123", "4567"]
*/
_splitStringAt: function(str, index) {
return [str.substring(0, index), str.substring(index, str.length)];
},
/**
* Check if the current input is displaying more than one line of text.
*

View file

@ -147,6 +147,7 @@ skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_html_tooltip_xul-wrapper.js]
[browser_inplace-editor-01.js]
[browser_inplace-editor-02.js]
[browser_inplace-editor_autoclose_parentheses.js]
[browser_inplace-editor_autocomplete_01.js]
[browser_inplace-editor_autocomplete_02.js]
[browser_inplace-editor_autocomplete_offset.js]

View file

@ -0,0 +1,73 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_inplace_editor.js */
"use strict";
const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
loadHelperScript("helper_inplace_editor.js");
// Test the inplace-editor closes parentheses automatically.
// format :
// [
// what key to press,
// expected input box value after keypress,
// selected suggestion index (-1 if popup is hidden),
// number of suggestions in the popup (0 if popup is hidden),
// ]
const testData = [
["u", "u", -1, 0],
["r", "ur", -1, 0],
["l", "url", -1, 0],
["(", "url()", -1, 0],
["v", "url(v)", -1, 0],
["a", "url(va)", -1, 0],
["r", "url(var)", -1, 0],
["(", "url(var())", -1, 0],
["-", "url(var(-))", -1, 0],
["-", "url(var(--))", -1, 0],
["a", "url(var(--a))", -1, 0],
[")", "url(var(--a))", -1, 0],
[")", "url(var(--a))", -1, 0],
];
add_task(async function() {
await addTab("data:text/html;charset=utf-8," +
"inplace editor parentheses autoclose");
let [host, win, doc] = await createHost();
let xulDocument = win.top.document;
let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
await new Promise(resolve => {
createInplaceEditorAndClick({
start: runPropertyAutocompletionTest,
contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
property: {
name: "background-image"
},
cssVariables: new Map(),
done: resolve,
popup: popup
}, doc);
});
popup.destroy();
host.destroy();
gBrowser.removeCurrentTab();
});
let runPropertyAutocompletionTest = async function(editor) {
info("Starting to test for css property completion");
// No need to test autocompletion here, return an empty array.
editor._getCSSValuesForPropertyName = () => [];
for (let data of testData) {
await testCompletion(data, editor);
}
EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
};

View file

@ -27,16 +27,16 @@ const testData = [
["v", "v", -1, 0, null],
["a", "va", -1, 0, null],
["r", "var", -1, 0, null],
["(", "var(", -1, 0, null],
["-", "var(--abc", 0, 4, "blue"],
["VK_BACK_SPACE", "var(-", -1, 0, null],
["-", "var(--abc", 0, 4, "blue"],
["VK_DOWN", "var(--def", 1, 4, "red"],
["VK_DOWN", "var(--ghi", 2, 4, "green"],
["VK_DOWN", "var(--jkl", 3, 4, "yellow"],
["VK_DOWN", "var(--abc", 0, 4, "blue"],
["VK_DOWN", "var(--def", 1, 4, "red"],
["VK_LEFT", "var(--def", -1, 0, null],
["(", "var()", -1, 0, null],
["-", "var(--abc)", 0, 4, "blue"],
["VK_BACK_SPACE", "var(-)", -1, 0, null],
["-", "var(--abc)", 0, 4, "blue"],
["VK_DOWN", "var(--def)", 1, 4, "red"],
["VK_DOWN", "var(--ghi)", 2, 4, "green"],
["VK_DOWN", "var(--jkl)", 3, 4, "yellow"],
["VK_DOWN", "var(--abc)", 0, 4, "blue"],
["VK_DOWN", "var(--def)", 1, 4, "red"],
["VK_LEFT", "var(--def)", -1, 0, null],
];
const CSS_VARIABLES = [