traceSearch.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import type {TraceTree} from '../traceModels/traceTree';
  2. import type {TraceTreeNode} from '../traceModels/traceTreeNode';
  3. import type {TraceSearchResult} from '../traceSearch/traceSearchEvaluator';
  4. import {traceReducerExhaustiveActionCheck} from '../traceState';
  5. export type TraceSearchAction =
  6. | {query: string | undefined; type: 'set query'}
  7. | {type: 'go to first match'}
  8. | {type: 'go to last match'}
  9. | {type: 'go to next match'}
  10. | {type: 'go to previous match'}
  11. | {
  12. resultIndex: number;
  13. resultIteratorIndex: number;
  14. type: 'set search iterator index';
  15. }
  16. | {type: 'clear'}
  17. | {type: 'clear search iterator index'}
  18. | {type: 'clear query'}
  19. | {
  20. node: TraceTreeNode<TraceTree.NodeValue> | null;
  21. previousNode: {
  22. resultIndex: number | undefined;
  23. resultIteratorIndex: number | undefined;
  24. } | null;
  25. results: ReadonlyArray<TraceSearchResult>;
  26. resultsLookup: Map<TraceTreeNode<TraceTree.NodeValue>, number>;
  27. type: 'set results';
  28. resultIndex?: number;
  29. resultIteratorIndex?: number;
  30. };
  31. export type TraceSearchState = {
  32. node: TraceTreeNode<TraceTree.NodeValue> | null;
  33. query: string | undefined;
  34. // Index in the list/tree
  35. resultIndex: number | null;
  36. // Index in the results array
  37. resultIteratorIndex: number | null;
  38. results: ReadonlyArray<TraceSearchResult> | null;
  39. resultsLookup: Map<TraceTreeNode<TraceTree.NodeValue>, number>;
  40. status: [ts: number, 'loading' | 'success' | 'error'] | undefined;
  41. };
  42. function assertBoundedIndex(index: number, length: number) {
  43. if (index < 0 || index > length - 1) {
  44. throw new Error('Search index out of bounds');
  45. }
  46. }
  47. export function traceSearchReducer(
  48. state: TraceSearchState,
  49. action: TraceSearchAction
  50. ): TraceSearchState {
  51. switch (action.type) {
  52. case 'clear query': {
  53. return {
  54. node: null,
  55. query: undefined,
  56. resultIteratorIndex: null,
  57. results: null,
  58. resultIndex: null,
  59. resultsLookup: new Map(),
  60. status: undefined,
  61. };
  62. }
  63. case 'go to first match': {
  64. if (!state.results || state.results.length === 0) {
  65. return state;
  66. }
  67. return {
  68. ...state,
  69. node: state.results[0].value,
  70. resultIteratorIndex: 0,
  71. resultIndex: state.results[0].index,
  72. };
  73. }
  74. case 'go to last match': {
  75. if (!state.results || state.results.length === 0) {
  76. return state;
  77. }
  78. return {
  79. ...state,
  80. resultIteratorIndex: state.results.length - 1,
  81. resultIndex: state.results[state.results.length - 1].index,
  82. node: state.results[state.results.length - 1].value,
  83. };
  84. }
  85. case 'go to next match': {
  86. if (state.resultIteratorIndex === null) {
  87. if (!state.results || state.results.length === 0) {
  88. return state;
  89. }
  90. return {
  91. ...state,
  92. resultIteratorIndex: 0,
  93. resultIndex: state.results[0].index,
  94. node: state.results[0].value,
  95. };
  96. }
  97. if (!state.results) {
  98. return state;
  99. }
  100. let next = state.resultIteratorIndex + 1;
  101. if (next > state.results.length - 1) {
  102. next = 0;
  103. }
  104. assertBoundedIndex(next, state.results.length);
  105. return {
  106. ...state,
  107. resultIteratorIndex: next,
  108. resultIndex: state.results[next].index,
  109. node: state.results[next].value,
  110. };
  111. }
  112. case 'go to previous match': {
  113. if (state.resultIteratorIndex === null) {
  114. if (!state.results || !state.results.length) {
  115. return state;
  116. }
  117. return {
  118. ...state,
  119. resultIteratorIndex: state.results.length - 1,
  120. resultIndex: state.results[state.results.length - 1].index,
  121. node: state.results[state.results.length - 1].value,
  122. };
  123. }
  124. if (!state.results) {
  125. return state;
  126. }
  127. let previous = state.resultIteratorIndex - 1;
  128. if (previous < 0) {
  129. previous = state.results.length - 1;
  130. }
  131. assertBoundedIndex(previous, state.results.length);
  132. return {
  133. ...state,
  134. resultIteratorIndex: previous,
  135. resultIndex: state.results[previous].index,
  136. node: state.results[previous].value,
  137. };
  138. }
  139. case 'set results': {
  140. return {
  141. ...state,
  142. status: [performance.now(), 'success'],
  143. results: action.results,
  144. resultIteratorIndex: action.resultIteratorIndex ?? null,
  145. node: action.node ?? null,
  146. resultIndex: action.resultIndex ?? null,
  147. resultsLookup: action.resultsLookup,
  148. };
  149. }
  150. case 'set query': {
  151. return {
  152. ...state,
  153. status: [performance.now(), 'loading'],
  154. query: action.query,
  155. };
  156. }
  157. case 'set search iterator index': {
  158. return {
  159. ...state,
  160. node: state.results?.[action.resultIteratorIndex]?.value ?? null,
  161. resultIteratorIndex: action.resultIteratorIndex,
  162. resultIndex: action.resultIndex,
  163. };
  164. }
  165. case 'clear search iterator index':
  166. return {
  167. ...state,
  168. resultIteratorIndex: null,
  169. resultIndex: null,
  170. node: null,
  171. };
  172. case 'clear': {
  173. return {...state, node: null, resultIteratorIndex: null, resultIndex: null};
  174. }
  175. default: {
  176. traceReducerExhaustiveActionCheck(action);
  177. return state;
  178. }
  179. }
  180. }