Răsfoiți Sursa

Improve performance of search match counting (#12808)

GitOrigin-RevId: 70cd1aaf5b079cbd540c4a9f92a04fcb2786b308
Alf Eaton 3 ani în urmă
părinte
comite
05582567b4

+ 73 - 36
services/web/frontend/js/features/source-editor/components/codemirror-search-form.tsx

@@ -22,8 +22,12 @@ import Icon from '../../../shared/components/icon'
 import classnames from 'classnames'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
 import { getStoredSelection, setStoredSelection } from '../extensions/search'
+import { debounce } from 'lodash'
+import { EditorState } from '@codemirror/state'
 
-const MAX_MATCH_COUNT = 1000
+const MATCH_COUNT_DEBOUNCE_WAIT = 100 // the amount of ms to wait before counting matches
+const MAX_MATCH_COUNT = 999 // the maximum number of matches to count
+const MAX_MATCH_TIME = 100 // the maximum amount of ms allowed for counting matches
 
 type ActiveSearchOption =
   | 'caseSensitive'
@@ -32,6 +36,12 @@ type ActiveSearchOption =
   | 'withinSelection'
   | null
 
+type MatchPositions = {
+  current: number | null
+  total: number
+  interrupted: boolean
+}
+
 const CodeMirrorSearchForm: FC = () => {
   const view = useCodeMirrorViewContext()
   const state = useCodeMirrorStateContext()
@@ -52,10 +62,7 @@ const CodeMirrorSearchForm: FC = () => {
 
   const { t } = useTranslation()
 
-  const [position, setPosition] = useState<{
-    current: number
-    total: number
-  } | null>(null)
+  const [position, setPosition] = useState<MatchPositions | null>(null)
 
   const formRef = useRef<HTMLFormElement | null>(null)
   const inputRef = useRef<HTMLInputElement | null>(null)
@@ -80,34 +87,7 @@ const CodeMirrorSearchForm: FC = () => {
   }, [])
 
   useEffect(() => {
-    const { from, to } = state.selection.main
-
-    const query = getSearchQuery(state)
-
-    if (query.valid) {
-      const cursor = query.getCursor(state.doc) as SearchCursor
-
-      let total = 0
-      let current = 0
-
-      while (!cursor.next().done) {
-        total++
-
-        if (total >= MAX_MATCH_COUNT) {
-          break
-        }
-
-        const item = cursor.value
-
-        if (current === 0 && item.from === from && item.to === to) {
-          current = total
-        }
-      }
-
-      setPosition({ current, total })
-    } else {
-      setPosition(null)
-    }
+    buildPosition(state, setPosition)
   }, [state])
 
   const handleChange = useCallback(() => {
@@ -435,9 +415,9 @@ const CodeMirrorSearchForm: FC = () => {
 
           {position !== null && (
             <div className="ol-cm-search-form-position">
-              {position.total === MAX_MATCH_COUNT
-                ? `${position.current} ${t('of')} ${MAX_MATCH_COUNT}+`
-                : `${position.current} ${t('of')} ${position.total}`}
+              {position.current === null ? '?' : position.current} {t('of')}{' '}
+              {position.total}
+              {position.interrupted && '+'}
             </div>
           )}
         </div>
@@ -483,3 +463,60 @@ function isInvalidRegExp(source: string) {
 }
 
 export default CodeMirrorSearchForm
+
+const buildPosition = debounce(
+  (
+    state: EditorState,
+    setPosition: (position: MatchPositions | null) => void
+  ) => {
+    const { main } = state.selection
+
+    const query = getSearchQuery(state)
+
+    if (!query.valid) {
+      return setPosition(null)
+    }
+
+    const cursor = query.getCursor(state.doc) as SearchCursor
+
+    const startTime = Date.now()
+
+    let total = 0
+    let current = null
+
+    while (!cursor.next().done) {
+      total++
+
+      // if there are too many matches, bail out
+      if (total >= MAX_MATCH_COUNT) {
+        return setPosition({
+          current,
+          total,
+          interrupted: true,
+        })
+      }
+
+      const { from, to } = cursor.value
+
+      if (current === null && main.from === from && main.to === to) {
+        current = total
+      }
+
+      // if finding matches is taking too long, bail out
+      if (Date.now() - startTime > MAX_MATCH_TIME) {
+        return setPosition({
+          current,
+          total,
+          interrupted: true,
+        })
+      }
+    }
+
+    setPosition({
+      current: current ?? 0,
+      total,
+      interrupted: false,
+    })
+  },
+  MATCH_COUNT_DEBOUNCE_WAIT
+)