244 lines
6.7 KiB
JavaScript
244 lines
6.7 KiB
JavaScript
/* 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/. */
|
|
"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;
|