Bug 1489491 - Populate reverse search with input text selection; r=bgrins.

If the user selected some text in the console input
and opens the reverse search UI, we populate the
reverse search input with the selected text and do
a reverse search with this text.
A test is added to ensure this works as expected.

Differential Revision: https://phabricator.services.mozilla.com/D16843

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nicolas Chevobbe 2019-01-18 13:20:44 +00:00
Родитель 88fc8a5784
Коммит 1128d243c6
8 изменённых файлов: 138 добавлений и 12 удалений

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

@ -103,9 +103,10 @@ function showObjectInSidebar(grip) {
}; };
} }
function reverseSearchInputToggle() { function reverseSearchInputToggle({initialValue} = {}) {
return { return {
type: REVERSE_SEARCH_INPUT_TOGGLE, type: REVERSE_SEARCH_INPUT_TOGGLE,
initialValue,
}; };
} }

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

@ -49,6 +49,7 @@ class App extends Component {
jstermCodeMirror: PropTypes.bool, jstermCodeMirror: PropTypes.bool,
currentReverseSearchEntry: PropTypes.string, currentReverseSearchEntry: PropTypes.string,
reverseSearchInputVisible: PropTypes.bool, reverseSearchInputVisible: PropTypes.bool,
reverseSearchInitialValue: PropTypes.string,
}; };
} }
@ -63,13 +64,15 @@ class App extends Component {
onKeyDown(event) { onKeyDown(event) {
const { const {
dispatch, dispatch,
hud,
} = this.props; } = this.props;
if ( if (
(!isMacOS && event.key === "F9") || (!isMacOS && event.key === "F9") ||
(isMacOS && event.key === "r" && event.ctrlKey === true) (isMacOS && event.key === "r" && event.ctrlKey === true)
) { ) {
dispatch(actions.reverseSearchInputToggle()); const initialValue = hud.jsterm && hud.jsterm.getSelectedText();
dispatch(actions.reverseSearchInputToggle({initialValue}));
event.stopPropagation(); event.stopPropagation();
} }
} }
@ -196,6 +199,7 @@ class App extends Component {
serviceContainer, serviceContainer,
closeSplitConsole, closeSplitConsole,
jstermCodeMirror, jstermCodeMirror,
reverseSearchInitialValue,
} = this.props; } = this.props;
const classNames = ["webconsole-output-wrapper"]; const classNames = ["webconsole-output-wrapper"];
@ -243,6 +247,7 @@ class App extends Component {
}), }),
ReverseSearchInput({ ReverseSearchInput({
hud, hud,
initialValue: reverseSearchInitialValue,
}) })
), ),
SideBar({ SideBar({
@ -261,6 +266,7 @@ class App extends Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
notifications: getAllNotifications(state), notifications: getAllNotifications(state),
reverseSearchInputVisible: state.ui.reverseSearchInputVisible, reverseSearchInputVisible: state.ui.reverseSearchInputVisible,
reverseSearchInitialValue: state.ui.reverseSearchInitialValue,
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

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

@ -734,6 +734,14 @@ class JSTerm extends Component {
return this.inputNode ? this.inputNode.selectionStart : null; return this.inputNode ? this.inputNode.selectionStart : null;
} }
getSelectedText() {
if (this.inputNode) {
return this.inputNode.value.substring(
this.inputNode.selectionStart, this.inputNode.selectionEnd);
}
return this.editor.getSelection();
}
/** /**
* Even handler for the "beforeChange" event fired by codeMirror. This event is fired * Even handler for the "beforeChange" event fired by codeMirror. This event is fired
* when codeMirror is about to make a change to its DOM representation. * when codeMirror is about to make a change to its DOM representation.

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

@ -33,6 +33,7 @@ class ReverseSearchInput extends Component {
reverseSearchTotalResults: PropTypes.number, reverseSearchTotalResults: PropTypes.number,
reverseSearchResultPosition: PropTypes.number, reverseSearchResultPosition: PropTypes.number,
visible: PropTypes.bool, visible: PropTypes.bool,
initialValue: PropTypes.string,
}; };
} }
@ -55,6 +56,14 @@ class ReverseSearchInput extends Component {
if (prevProps.visible === true && this.props.visible === false) { if (prevProps.visible === true && this.props.visible === false) {
jsterm.focus(); jsterm.focus();
} }
if (
prevProps.visible === false &&
this.props.visible === true &&
this.props.initialValue
) {
this.inputNode.value = this.props.initialValue;
}
} }
onInputKeyDown(event) { onInputKeyDown(event) {

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

@ -54,7 +54,7 @@ function history(state = getInitialState(), action, prefsState) {
case UPDATE_HISTORY_POSITION: case UPDATE_HISTORY_POSITION:
return updateHistoryPosition(state, action.direction, action.expression); return updateHistoryPosition(state, action.direction, action.expression);
case REVERSE_SEARCH_INPUT_TOGGLE: case REVERSE_SEARCH_INPUT_TOGGLE:
return reverseSearchInputToggle(state); return reverseSearchInputToggle(state, action);
case REVERSE_SEARCH_INPUT_CHANGE: case REVERSE_SEARCH_INPUT_CHANGE:
return reverseSearchInputChange(state, action.value); return reverseSearchInputChange(state, action.value);
case REVERSE_SEARCH_BACK: case REVERSE_SEARCH_BACK:
@ -143,14 +143,23 @@ function updateHistoryPosition(state, direction, expression) {
return state; return state;
} }
function reverseSearchInputToggle(state) { function reverseSearchInputToggle(state, action) {
return { const { initialValue = "" } = action;
...state,
reverseSearchEnabled: !state.reverseSearchEnabled, // We're going to close the reverse search, let's clean the state
position: state.reverseSearchEnabled === true ? state.entries.length : undefined, if (state.reverseSearchEnabled) {
currentReverseSearchResults: null, return {
currentReverseSearchResultsPosition: null, ...state,
}; reverseSearchEnabled: false,
position: undefined,
currentReverseSearchResults: null,
currentReverseSearchResultsPosition: null,
};
}
// If we're enabling the reverse search, we treat it as a reverse search input change,
// since we can have an initial value.
return reverseSearchInputChange(state, initialValue);
} }
function reverseSearchInputChange(state, searchString) { function reverseSearchInputChange(state, searchString) {

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

@ -32,6 +32,7 @@ const UiState = (overrides) => Object.freeze(Object.assign({
gripInSidebar: null, gripInSidebar: null,
closeButtonVisible: false, closeButtonVisible: false,
reverseSearchInputVisible: false, reverseSearchInputVisible: false,
reverseSearchInitialValue: "",
}, overrides)); }, overrides));
function ui(state = UiState(), action) { function ui(state = UiState(), action) {
@ -61,7 +62,11 @@ function ui(state = UiState(), action) {
case SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE: case SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE:
return Object.assign({}, state, {closeButtonVisible: action.shouldDisplayButton}); return Object.assign({}, state, {closeButtonVisible: action.shouldDisplayButton});
case REVERSE_SEARCH_INPUT_TOGGLE: case REVERSE_SEARCH_INPUT_TOGGLE:
return {...state, reverseSearchInputVisible: !state.reverseSearchInputVisible}; return {
...state,
reverseSearchInputVisible: !state.reverseSearchInputVisible,
reverseSearchInitialValue: action.initialValue || "",
};
} }
return state; return state;

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

@ -374,6 +374,7 @@ subsuite = clipboard
[browser_webconsole_reopen_closed_tab.js] [browser_webconsole_reopen_closed_tab.js]
[browser_webconsole_repeat_different_objects.js] [browser_webconsole_repeat_different_objects.js]
[browser_webconsole_reverse_search.js] [browser_webconsole_reverse_search.js]
[browser_webconsole_reverse_search_initial_value.js]
[browser_webconsole_reverse_search_keyboard_navigation.js] [browser_webconsole_reverse_search_keyboard_navigation.js]
[browser_webconsole_reverse_search_mouse_navigation.js] [browser_webconsole_reverse_search_mouse_navigation.js]
[browser_webconsole_reverse_search_toggle.js] [browser_webconsole_reverse_search_toggle.js]

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

@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Tests reverse search features.
"use strict";
const TEST_URI = `data:text/html,<meta charset=utf8>Test reverse search initial value`;
add_task(async function() {
// Run test with legacy JsTerm
await pushPref("devtools.webconsole.jsterm.codeMirror", false);
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const { jsterm } = hud;
const jstermHistory = [
`Dog = "Snoopy"`,
`document
.querySelectorAll("*")
.forEach(console.log)`,
`document`,
`"😎"`,
];
const onLastMessage = waitForMessage(hud, `"😎"`);
for (const input of jstermHistory) {
await jsterm.execute(input);
}
await onLastMessage;
const jstermInitialValue = "ado";
jsterm.setInputValue(jstermInitialValue);
info(`Select 2 chars ("do") from the input`);
if (jsterm.inputNode) {
jsterm.inputNode.selectionStart = 1;
jsterm.inputNode.selectionEnd = 3;
} else {
jsterm.editor.setSelection({line: 0, ch: 1}, {line: 0, ch: 3});
}
info("Check that the reverse search toolbar as the expected initial state");
let reverseSearchElement = await openReverseSearch(hud);
is(reverseSearchElement.querySelector("input").value, "do",
`Reverse search input has expected "do" value`);
is(isReverseSearchInputFocused(hud), true, "reverse search input is focused");
ok(reverseSearchElement, "Reverse search is displayed with a keyboard shortcut");
const infoElement = getReverseSearchInfoElement(hud);
is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text");
const previousButton = reverseSearchElement.querySelector(".search-result-button-prev");
const nextButton = reverseSearchElement.querySelector(".search-result-button-next");
ok(previousButton, "Previous navigation button is displayed");
ok(nextButton, "Next navigation button is displayed");
is(jsterm.getInputValue(), "document", "JsTerm has the expected input");
is(jsterm.autocompletePopup.isOpen, false,
"Setting the input value did not trigger the autocompletion");
const onJsTermValueChanged = jsterm.once("set-input-value");
EventUtils.sendString("g");
await onJsTermValueChanged;
is(jsterm.getInputValue(), `Dog = "Snoopy"`, "JsTerm input was updated");
is(infoElement.textContent, "1 result", "The reverse info has the expected text");
ok(
!reverseSearchElement.querySelector(".search-result-button-prev") &&
!reverseSearchElement.querySelector(".search-result-button-next"),
"The results navigation buttons are not displayed when there's only one result"
);
info("Check that there's no initial value when no text is selected");
EventUtils.synthesizeKey("KEY_Escape");
await waitFor(() => !getReverseSearchElement(hud));
info("Check that opening the reverse search input is empty after opening it again");
reverseSearchElement = await openReverseSearch(hud);
is(reverseSearchElement.querySelector("input").value, "",
"Reverse search input is empty");
}