history.js (6900B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { 7 APPEND_TO_HISTORY, 8 CLEAR_HISTORY, 9 EVALUATE_EXPRESSION, 10 HISTORY_LOADED, 11 UPDATE_HISTORY_POSITION, 12 HISTORY_BACK, 13 HISTORY_FORWARD, 14 REVERSE_SEARCH_INPUT_TOGGLE, 15 REVERSE_SEARCH_INPUT_CHANGE, 16 REVERSE_SEARCH_BACK, 17 REVERSE_SEARCH_NEXT, 18 SET_TERMINAL_INPUT, 19 SET_TERMINAL_EAGER_RESULT, 20 } = require("resource://devtools/client/webconsole/constants.js"); 21 22 /** 23 * Create default initial state for this reducer. 24 */ 25 function getInitialState() { 26 return { 27 // Array with history entries 28 entries: [], 29 30 // Holds position (index) in history entries that the user is 31 // currently viewing. This is reset to this.entries.length when 32 // APPEND_TO_HISTORY action is fired. 33 position: undefined, 34 35 // Backups the original user value (if any) that can be set in 36 // the input field. It might be used again if the user doesn't 37 // pick up anything from the history and wants to return all 38 // the way back to see the original input text. 39 originalUserValue: null, 40 41 reverseSearchEnabled: false, 42 currentReverseSearchResults: null, 43 currentReverseSearchResultsPosition: null, 44 45 terminalInput: null, 46 terminalEagerResult: null, 47 }; 48 } 49 50 function history(state = getInitialState(), action, prefsState) { 51 switch (action.type) { 52 case APPEND_TO_HISTORY: 53 case EVALUATE_EXPRESSION: 54 return appendToHistory(state, prefsState, action.expression); 55 case CLEAR_HISTORY: 56 return clearHistory(state); 57 case HISTORY_LOADED: 58 return historyLoaded(state, action.entries); 59 case UPDATE_HISTORY_POSITION: 60 return updateHistoryPosition(state, action.direction, action.expression); 61 case REVERSE_SEARCH_INPUT_TOGGLE: 62 return reverseSearchInputToggle(state, action); 63 case REVERSE_SEARCH_INPUT_CHANGE: 64 return reverseSearchInputChange(state, action.value); 65 case REVERSE_SEARCH_BACK: 66 return reverseSearchBack(state); 67 case REVERSE_SEARCH_NEXT: 68 return reverseSearchNext(state); 69 case SET_TERMINAL_INPUT: 70 return setTerminalInput(state, action.expression); 71 case SET_TERMINAL_EAGER_RESULT: 72 return setTerminalEagerResult(state, action.result); 73 } 74 return state; 75 } 76 77 function appendToHistory(state, prefsState, expression) { 78 // Clone state 79 state = { ...state }; 80 state.entries = [...state.entries]; 81 82 // Append new expression only if it isn't the same as 83 // the one recently added. 84 if (expression.trim() != state.entries[state.entries.length - 1]) { 85 state.entries.push(expression); 86 } 87 88 // Remove entries if the limit is reached 89 if (state.entries.length > prefsState.historyCount) { 90 state.entries.splice(0, state.entries.length - prefsState.historyCount); 91 } 92 93 state.position = state.entries.length; 94 state.originalUserValue = null; 95 96 return state; 97 } 98 99 function clearHistory() { 100 return getInitialState(); 101 } 102 103 /** 104 * Handling HISTORY_LOADED action that is fired when history 105 * entries created in previous Firefox session are loaded 106 * from async-storage. 107 * 108 * Loaded entries are appended before the ones that were 109 * added to the state in this session. 110 */ 111 function historyLoaded(state, entries) { 112 const newEntries = [...entries, ...state.entries]; 113 return { 114 ...state, 115 entries: newEntries, 116 // Default position is at the end of the list 117 // (at the latest inserted item). 118 position: newEntries.length, 119 originalUserValue: null, 120 }; 121 } 122 123 function updateHistoryPosition(state, direction, expression) { 124 // Handle UP arrow key => HISTORY_BACK 125 // Handle DOWN arrow key => HISTORY_FORWARD 126 if (direction == HISTORY_BACK) { 127 if (state.position <= 0) { 128 return state; 129 } 130 131 // Clone state 132 state = { ...state }; 133 134 // Store the current input value when the user starts 135 // browsing through the history. 136 if (state.position == state.entries.length) { 137 state.originalUserValue = expression || ""; 138 } 139 140 state.position--; 141 } else if (direction == HISTORY_FORWARD) { 142 if (state.position >= state.entries.length) { 143 return state; 144 } 145 146 state = { 147 ...state, 148 position: state.position + 1, 149 }; 150 } 151 152 return state; 153 } 154 155 function reverseSearchInputToggle(state, action) { 156 const { initialValue = "" } = action; 157 158 // We're going to close the reverse search, let's clean the state 159 if (state.reverseSearchEnabled) { 160 return { 161 ...state, 162 reverseSearchEnabled: false, 163 position: undefined, 164 currentReverseSearchResults: null, 165 currentReverseSearchResultsPosition: null, 166 }; 167 } 168 169 // If we're enabling the reverse search, we treat it as a reverse search input change, 170 // since we can have an initial value. 171 return reverseSearchInputChange(state, initialValue); 172 } 173 174 function reverseSearchInputChange(state, searchString) { 175 if (searchString === "") { 176 return { 177 ...state, 178 position: undefined, 179 currentReverseSearchResults: null, 180 currentReverseSearchResultsPosition: null, 181 }; 182 } 183 184 searchString = searchString.toLocaleLowerCase(); 185 const matchingEntries = state.entries.filter(entry => 186 entry.toLocaleLowerCase().includes(searchString) 187 ); 188 // We only return unique entries, but we want to keep the latest entry in the array if 189 // it's duplicated (e.g. if we have [1,2,1], we want to get [2,1], not [1,2]). 190 // To do that, we need to reverse the matching entries array, provide it to a Set, 191 // transform it back to an array and reverse it again. 192 const uniqueEntries = new Set(matchingEntries.reverse()); 193 const currentReverseSearchResults = Array.from( 194 new Set(uniqueEntries) 195 ).reverse(); 196 197 return { 198 ...state, 199 position: undefined, 200 currentReverseSearchResults, 201 currentReverseSearchResultsPosition: currentReverseSearchResults.length - 1, 202 }; 203 } 204 205 function reverseSearchBack(state) { 206 let nextPosition = state.currentReverseSearchResultsPosition - 1; 207 if (nextPosition < 0) { 208 nextPosition = state.currentReverseSearchResults.length - 1; 209 } 210 211 return { 212 ...state, 213 currentReverseSearchResultsPosition: nextPosition, 214 }; 215 } 216 217 function reverseSearchNext(state) { 218 let previousPosition = state.currentReverseSearchResultsPosition + 1; 219 if (previousPosition >= state.currentReverseSearchResults.length) { 220 previousPosition = 0; 221 } 222 223 return { 224 ...state, 225 currentReverseSearchResultsPosition: previousPosition, 226 }; 227 } 228 229 function setTerminalInput(state, expression) { 230 return { 231 ...state, 232 terminalInput: expression, 233 terminalEagerResult: !expression ? null : state.terminalEagerResult, 234 }; 235 } 236 237 function setTerminalEagerResult(state, result) { 238 return { 239 ...state, 240 terminalEagerResult: result, 241 }; 242 } 243 244 exports.history = history;