traceSearch.tsx 5.2 KB

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