Source code
Revision control
Copy as Markdown
Other Tools
/* 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
"use strict";
const {
APPEND_TO_HISTORY,
CLEAR_HISTORY,
EVALUATE_EXPRESSION,
HISTORY_LOADED,
UPDATE_HISTORY_POSITION,
HISTORY_BACK,
HISTORY_FORWARD,
REVERSE_SEARCH_INPUT_TOGGLE,
REVERSE_SEARCH_INPUT_CHANGE,
REVERSE_SEARCH_BACK,
REVERSE_SEARCH_NEXT,
SET_TERMINAL_INPUT,
SET_TERMINAL_EAGER_RESULT,
} = require("resource://devtools/client/webconsole/constants.js");
/**
* Create default initial state for this reducer.
*/
function getInitialState() {
return {
// Array with history entries
entries: [],
// Holds position (index) in history entries that the user is
// currently viewing. This is reset to this.entries.length when
// APPEND_TO_HISTORY action is fired.
position: undefined,
// Backups the original user value (if any) that can be set in
// the input field. It might be used again if the user doesn't
// pick up anything from the history and wants to return all
// the way back to see the original input text.
originalUserValue: null,
reverseSearchEnabled: false,
currentReverseSearchResults: null,
currentReverseSearchResultsPosition: null,
terminalInput: null,
terminalEagerResult: null,
};
}
function history(state = getInitialState(), action, prefsState) {
switch (action.type) {
case APPEND_TO_HISTORY:
case EVALUATE_EXPRESSION:
return appendToHistory(state, prefsState, action.expression);
case CLEAR_HISTORY:
return clearHistory(state);
case HISTORY_LOADED:
return historyLoaded(state, action.entries);
case UPDATE_HISTORY_POSITION:
return updateHistoryPosition(state, action.direction, action.expression);
case REVERSE_SEARCH_INPUT_TOGGLE:
return reverseSearchInputToggle(state, action);
case REVERSE_SEARCH_INPUT_CHANGE:
return reverseSearchInputChange(state, action.value);
case REVERSE_SEARCH_BACK:
return reverseSearchBack(state);
case REVERSE_SEARCH_NEXT:
return reverseSearchNext(state);
case SET_TERMINAL_INPUT:
return setTerminalInput(state, action.expression);
case SET_TERMINAL_EAGER_RESULT:
return setTerminalEagerResult(state, action.result);
}
return state;
}
function appendToHistory(state, prefsState, expression) {
// Clone state
state = { ...state };
state.entries = [...state.entries];
// Append new expression only if it isn't the same as
// the one recently added.
if (expression.trim() != state.entries[state.entries.length - 1]) {
state.entries.push(expression);
}
// Remove entries if the limit is reached
if (state.entries.length > prefsState.historyCount) {
state.entries.splice(0, state.entries.length - prefsState.historyCount);
}
state.position = state.entries.length;
state.originalUserValue = null;
return state;
}
function clearHistory() {
return getInitialState();
}
/**
* Handling HISTORY_LOADED action that is fired when history
* entries created in previous Firefox session are loaded
* from async-storage.
*
* Loaded entries are appended before the ones that were
* added to the state in this session.
*/
function historyLoaded(state, entries) {
const newEntries = [...entries, ...state.entries];
return {
...state,
entries: newEntries,
// Default position is at the end of the list
// (at the latest inserted item).
position: newEntries.length,
originalUserValue: null,
};
}
function updateHistoryPosition(state, direction, expression) {
// Handle UP arrow key => HISTORY_BACK
// Handle DOWN arrow key => HISTORY_FORWARD
if (direction == HISTORY_BACK) {
if (state.position <= 0) {
return state;
}
// Clone state
state = { ...state };
// Store the current input value when the user starts
// browsing through the history.
if (state.position == state.entries.length) {
state.originalUserValue = expression || "";
}
state.position--;
} else if (direction == HISTORY_FORWARD) {
if (state.position >= state.entries.length) {
return state;
}
state = {
...state,
position: state.position + 1,
};
}
return state;
}
function reverseSearchInputToggle(state, action) {
const { initialValue = "" } = action;
// We're going to close the reverse search, let's clean the state
if (state.reverseSearchEnabled) {
return {
...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) {
if (searchString === "") {
return {
...state,
position: undefined,
currentReverseSearchResults: null,
currentReverseSearchResultsPosition: null,
};
}
searchString = searchString.toLocaleLowerCase();
const matchingEntries = state.entries.filter(entry =>
entry.toLocaleLowerCase().includes(searchString)
);
// We only return unique entries, but we want to keep the latest entry in the array if
// it's duplicated (e.g. if we have [1,2,1], we want to get [2,1], not [1,2]).
// To do that, we need to reverse the matching entries array, provide it to a Set,
// transform it back to an array and reverse it again.
const uniqueEntries = new Set(matchingEntries.reverse());
const currentReverseSearchResults = Array.from(
new Set(uniqueEntries)
).reverse();
return {
...state,
position: undefined,
currentReverseSearchResults,
currentReverseSearchResultsPosition: currentReverseSearchResults.length - 1,
};
}
function reverseSearchBack(state) {
let nextPosition = state.currentReverseSearchResultsPosition - 1;
if (nextPosition < 0) {
nextPosition = state.currentReverseSearchResults.length - 1;
}
return {
...state,
currentReverseSearchResultsPosition: nextPosition,
};
}
function reverseSearchNext(state) {
let previousPosition = state.currentReverseSearchResultsPosition + 1;
if (previousPosition >= state.currentReverseSearchResults.length) {
previousPosition = 0;
}
return {
...state,
currentReverseSearchResultsPosition: previousPosition,
};
}
function setTerminalInput(state, expression) {
return {
...state,
terminalInput: expression,
terminalEagerResult: !expression ? null : state.terminalEagerResult,
};
}
function setTerminalEagerResult(state, result) {
return {
...state,
terminalEagerResult: result,
};
}
exports.history = history;