@tiptap/extension-find-and-replace 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +18 -0
  3. package/dist/index.cjs +950 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +230 -0
  6. package/dist/index.d.ts +230 -0
  7. package/dist/index.js +915 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +55 -0
  10. package/src/constants/constants.ts +2 -0
  11. package/src/find-and-replace.ts +434 -0
  12. package/src/index.ts +8 -0
  13. package/src/plugin/plugin-state.ts +27 -0
  14. package/src/plugin/plugin.ts +60 -0
  15. package/src/search/matches.ts +26 -0
  16. package/src/search/regex.ts +67 -0
  17. package/src/search/search.ts +70 -0
  18. package/src/search/text-segments.ts +63 -0
  19. package/src/search/textblock-search.ts +31 -0
  20. package/src/search/types.ts +17 -0
  21. package/src/state/debounced-search-state.ts +39 -0
  22. package/src/style/create-style-tag.ts +23 -0
  23. package/src/style/style.ts +10 -0
  24. package/src/types.ts +96 -0
  25. package/src/utils/createDecoration.ts +14 -0
  26. package/src/utils/createDecorations.ts +16 -0
  27. package/src/utils/createState.ts +23 -0
  28. package/src/utils/findDecorations.ts +27 -0
  29. package/src/utils/getChangedTextblocks.ts +29 -0
  30. package/src/utils/getFallbackCurrentIndex.ts +8 -0
  31. package/src/utils/getMappedCurrentIndex.ts +32 -0
  32. package/src/utils/getMetaCurrentIndex.ts +19 -0
  33. package/src/utils/getPreviousCurrentResult.ts +10 -0
  34. package/src/utils/getResultsToRefresh.ts +22 -0
  35. package/src/utils/getSearchCurrentIndex.ts +24 -0
  36. package/src/utils/getTextNodeResult.ts +21 -0
  37. package/src/utils/groupResults.ts +68 -0
  38. package/src/utils/hasCurrentIndex.ts +5 -0
  39. package/src/utils/index.ts +2 -0
  40. package/src/utils/isCurrentResult.ts +7 -0
  41. package/src/utils/isNewSearch.ts +5 -0
  42. package/src/utils/mapResult.ts +10 -0
  43. package/src/utils/mapResults.ts +19 -0
  44. package/src/utils/mergeResults.ts +18 -0
  45. package/src/utils/normalizeCurrentIndex.ts +10 -0
  46. package/src/utils/refreshCurrentIndexState.ts +30 -0
  47. package/src/utils/refreshDecorations.ts +28 -0
  48. package/src/utils/refreshDocumentState.ts +47 -0
  49. package/src/utils/refreshState.ts +27 -0
  50. package/src/utils/replaceAllResults.ts +18 -0
  51. package/src/utils/replaceGroup.ts +54 -0
  52. package/src/utils/resolveCurrentIndex.ts +27 -0
  53. package/src/utils/resultInTextblocks.ts +30 -0
  54. package/src/utils/sameResult.ts +5 -0
  55. package/src/utils/shouldRefreshResult.ts +18 -0
  56. package/src/utils/touchesSearch.ts +7 -0
  57. package/src/utils/types.ts +11 -0
  58. package/src/utils/updateState.ts +33 -0
@@ -0,0 +1,32 @@
1
+ import type { Mapping } from '@tiptap/pm/transform'
2
+
3
+ import type { FindAndReplacePluginState } from '../plugin/plugin-state.js'
4
+ import type { SearchResult } from '../search/search.js'
5
+ import { findNextIndex } from '../search/search.js'
6
+
7
+ import { getPreviousCurrentResult } from './getPreviousCurrentResult.js'
8
+
9
+ export function getMappedCurrentIndex(
10
+ previousState: FindAndReplacePluginState,
11
+ results: SearchResult[],
12
+ docChanged: boolean,
13
+ mapping: Mapping | undefined,
14
+ ): number | null | undefined {
15
+ if (!docChanged) {
16
+ return undefined
17
+ }
18
+
19
+ if (!mapping) {
20
+ return undefined
21
+ }
22
+
23
+ const previousResult = getPreviousCurrentResult(previousState)
24
+
25
+ if (!previousResult) {
26
+ return undefined
27
+ }
28
+
29
+ const mappedFrom = mapping.map(previousResult.from, 1)
30
+
31
+ return findNextIndex(results, mappedFrom)
32
+ }
@@ -0,0 +1,19 @@
1
+ import type { FindAndReplaceMeta } from '../plugin/plugin-state.js'
2
+
3
+ import { hasCurrentIndex } from './hasCurrentIndex.js'
4
+ import { normalizeCurrentIndex } from './normalizeCurrentIndex.js'
5
+
6
+ export function getMetaCurrentIndex(
7
+ meta: FindAndReplaceMeta | undefined,
8
+ length: number,
9
+ ): number | null | undefined {
10
+ if (!meta) {
11
+ return undefined
12
+ }
13
+
14
+ if (!hasCurrentIndex(meta)) {
15
+ return undefined
16
+ }
17
+
18
+ return normalizeCurrentIndex(meta.currentIndex, length)
19
+ }
@@ -0,0 +1,10 @@
1
+ import type { FindAndReplacePluginState } from '../plugin/plugin-state.js'
2
+ import type { SearchResult } from '../search/search.js'
3
+
4
+ export function getPreviousCurrentResult(
5
+ previousState: FindAndReplacePluginState,
6
+ ): SearchResult | undefined {
7
+ return previousState.currentIndex === null
8
+ ? undefined
9
+ : previousState.results[previousState.currentIndex]
10
+ }
@@ -0,0 +1,22 @@
1
+ import type { SearchResult } from '../search/search.js'
2
+
3
+ import { shouldRefreshResult } from './shouldRefreshResult.js'
4
+ import type { IndexedResult, TextblockRange } from './types.js'
5
+
6
+ export function getResultsToRefresh(
7
+ results: SearchResult[],
8
+ textblocks: readonly TextblockRange[],
9
+ previousCurrent: SearchResult | null,
10
+ currentIndex: number | null,
11
+ ): IndexedResult[] {
12
+ const current = currentIndex === null ? null : (results[currentIndex] ?? null)
13
+ const refreshedResults: IndexedResult[] = []
14
+
15
+ results.forEach((result, index) => {
16
+ if (shouldRefreshResult(result, textblocks, previousCurrent, current)) {
17
+ refreshedResults.push({ result, index })
18
+ }
19
+ })
20
+
21
+ return refreshedResults
22
+ }
@@ -0,0 +1,24 @@
1
+ import type { Mapping } from '@tiptap/pm/transform'
2
+
3
+ import type { FindAndReplaceMeta, FindAndReplacePluginState } from '../plugin/plugin-state.js'
4
+ import type { SearchResult } from '../search/search.js'
5
+
6
+ import { getFallbackCurrentIndex } from './getFallbackCurrentIndex.js'
7
+ import { getMappedCurrentIndex } from './getMappedCurrentIndex.js'
8
+ import { isNewSearch } from './isNewSearch.js'
9
+
10
+ export function getSearchCurrentIndex(
11
+ previousState: FindAndReplacePluginState,
12
+ results: SearchResult[],
13
+ meta: FindAndReplaceMeta | undefined,
14
+ docChanged: boolean,
15
+ mapping: Mapping | undefined,
16
+ ): number {
17
+ if (isNewSearch(meta)) {
18
+ return 0
19
+ }
20
+
21
+ const mappedIndex = getMappedCurrentIndex(previousState, results, docChanged, mapping)
22
+
23
+ return mappedIndex ?? getFallbackCurrentIndex(previousState, results.length)
24
+ }
@@ -0,0 +1,21 @@
1
+ import type { EditorState } from '@tiptap/pm/state'
2
+
3
+ import type { SearchResult } from '../search/search.js'
4
+
5
+ export interface TextNodeResult {
6
+ from: number
7
+ text: string
8
+ }
9
+
10
+ export function getTextNodeResult(state: EditorState, result: SearchResult): TextNodeResult | null {
11
+ const $from = state.doc.resolve(result.from)
12
+ const node = $from.parent.child($from.index())
13
+
14
+ if (!node.isText || !node.text) {
15
+ return null
16
+ }
17
+
18
+ const from = result.from - $from.textOffset
19
+
20
+ return result.to <= from + node.nodeSize ? { from, text: node.text } : null
21
+ }
@@ -0,0 +1,68 @@
1
+ import type { EditorState } from '@tiptap/pm/state'
2
+
3
+ import type { SearchResult } from '../search/search.js'
4
+ import type { TextNodeResult } from './getTextNodeResult.js'
5
+ import { getTextNodeResult } from './getTextNodeResult.js'
6
+
7
+ export interface ResultGroup {
8
+ from: number
9
+ text: string | null
10
+ results: SearchResult[]
11
+ }
12
+
13
+ function shouldMergeResult(
14
+ group: ResultGroup | undefined,
15
+ textNode: TextNodeResult | null,
16
+ ): boolean {
17
+ if (!group || !textNode) {
18
+ return false
19
+ }
20
+
21
+ return group.from === textNode.from && group.text !== null
22
+ }
23
+
24
+ function createTextResultGroup(result: SearchResult, textNode: TextNodeResult): ResultGroup {
25
+ return {
26
+ from: textNode.from,
27
+ text: textNode.text,
28
+ results: [result],
29
+ }
30
+ }
31
+
32
+ function createResultGroup(result: SearchResult): ResultGroup {
33
+ return {
34
+ from: result.from,
35
+ text: null,
36
+ results: [result],
37
+ }
38
+ }
39
+
40
+ function appendResult(
41
+ groups: ResultGroup[],
42
+ result: SearchResult,
43
+ textNode: TextNodeResult | null,
44
+ ): void {
45
+ const group = groups.at(-1)
46
+
47
+ if (shouldMergeResult(group, textNode)) {
48
+ group!.results.push(result)
49
+ return
50
+ }
51
+
52
+ if (textNode) {
53
+ groups.push(createTextResultGroup(result, textNode))
54
+ return
55
+ }
56
+
57
+ groups.push(createResultGroup(result))
58
+ }
59
+
60
+ export function groupResults(state: EditorState, results: SearchResult[]): ResultGroup[] {
61
+ const groups: ResultGroup[] = []
62
+
63
+ for (const result of results) {
64
+ appendResult(groups, result, getTextNodeResult(state, result))
65
+ }
66
+
67
+ return groups
68
+ }
@@ -0,0 +1,5 @@
1
+ import type { FindAndReplaceMeta } from '../plugin/plugin-state.js'
2
+
3
+ export function hasCurrentIndex(meta: FindAndReplaceMeta | undefined): boolean {
4
+ return !!meta && 'currentIndex' in meta
5
+ }
@@ -0,0 +1,2 @@
1
+ export { createState } from './createState.js'
2
+ export { updateState } from './updateState.js'
@@ -0,0 +1,7 @@
1
+ import type { SearchResult } from '../search/search.js'
2
+
3
+ import { sameResult } from './sameResult.js'
4
+
5
+ export function isCurrentResult(result: SearchResult, current: SearchResult | null): boolean {
6
+ return current !== null && sameResult(result, current)
7
+ }
@@ -0,0 +1,5 @@
1
+ import type { FindAndReplaceMeta } from '../plugin/plugin-state.js'
2
+
3
+ export function isNewSearch(meta: FindAndReplaceMeta | undefined): boolean {
4
+ return !!meta && 'searchTerm' in meta
5
+ }
@@ -0,0 +1,10 @@
1
+ import type { Mapping } from '@tiptap/pm/transform'
2
+
3
+ import type { SearchResult } from '../search/search.js'
4
+
5
+ export function mapResult(result: SearchResult, mapping: Mapping): SearchResult | null {
6
+ const from = mapping.map(result.from, 1)
7
+ const to = mapping.map(result.to, -1)
8
+
9
+ return from < to ? { from, to } : null
10
+ }
@@ -0,0 +1,19 @@
1
+ import type { Mapping } from '@tiptap/pm/transform'
2
+
3
+ import type { SearchResult } from '../search/search.js'
4
+
5
+ import { mapResult } from './mapResult.js'
6
+
7
+ export function mapResults(results: SearchResult[], mapping: Mapping): SearchResult[] {
8
+ const mappedResults: SearchResult[] = []
9
+
10
+ results.forEach(result => {
11
+ const mapped = mapResult(result, mapping)
12
+
13
+ if (mapped) {
14
+ mappedResults.push(mapped)
15
+ }
16
+ })
17
+
18
+ return mappedResults
19
+ }
@@ -0,0 +1,18 @@
1
+ import type { SearchResult } from '../search/search.js'
2
+
3
+ export function mergeResults(first: SearchResult[], second: SearchResult[]): SearchResult[] {
4
+ const results: SearchResult[] = []
5
+ let firstIndex = 0
6
+ let secondIndex = 0
7
+
8
+ while (firstIndex < first.length && secondIndex < second.length) {
9
+ if (first[firstIndex].from < second[secondIndex].from) {
10
+ results.push(first[firstIndex++])
11
+ continue
12
+ }
13
+
14
+ results.push(second[secondIndex++])
15
+ }
16
+
17
+ return results.concat(first.slice(firstIndex), second.slice(secondIndex))
18
+ }
@@ -0,0 +1,10 @@
1
+ export function normalizeCurrentIndex(
2
+ currentIndex: number | null | undefined,
3
+ length: number,
4
+ ): number | null {
5
+ if (currentIndex === undefined || currentIndex === null || length === 0) {
6
+ return null
7
+ }
8
+
9
+ return Math.max(0, Math.min(currentIndex, length - 1))
10
+ }
@@ -0,0 +1,30 @@
1
+ import type { Node } from '@tiptap/pm/model'
2
+
3
+ import type { FindAndReplaceMeta, FindAndReplacePluginState } from '../plugin/plugin-state.js'
4
+
5
+ import { refreshDecorations } from './refreshDecorations.js'
6
+ import { resolveCurrentIndex } from './resolveCurrentIndex.js'
7
+
8
+ export function refreshCurrentIndexState(
9
+ previousState: FindAndReplacePluginState,
10
+ state: FindAndReplacePluginState,
11
+ doc: Node,
12
+ meta: FindAndReplaceMeta | undefined,
13
+ ): FindAndReplacePluginState {
14
+ const currentIndex = resolveCurrentIndex(previousState, state.results, meta, false, undefined)
15
+ const previousCurrent =
16
+ previousState.currentIndex === null ? null : previousState.results[previousState.currentIndex]
17
+
18
+ return {
19
+ ...state,
20
+ currentIndex,
21
+ decorations: refreshDecorations(
22
+ previousState.decorations,
23
+ doc,
24
+ state.results,
25
+ currentIndex,
26
+ [],
27
+ previousCurrent,
28
+ ),
29
+ }
30
+ }
@@ -0,0 +1,28 @@
1
+ import type { Node } from '@tiptap/pm/model'
2
+ import { DecorationSet } from '@tiptap/pm/view'
3
+
4
+ import type { SearchResult } from '../search/search.js'
5
+
6
+ import { createDecoration } from './createDecoration.js'
7
+ import { findDecorations } from './findDecorations.js'
8
+ import { getResultsToRefresh } from './getResultsToRefresh.js'
9
+ import type { TextblockRange } from './types.js'
10
+
11
+ export function refreshDecorations(
12
+ decorations: DecorationSet,
13
+ doc: Node,
14
+ results: SearchResult[],
15
+ currentIndex: number | null,
16
+ textblocks: readonly TextblockRange[],
17
+ previousCurrent: SearchResult | null,
18
+ ): DecorationSet {
19
+ const resultsToRefresh = getResultsToRefresh(results, textblocks, previousCurrent, currentIndex)
20
+ const refreshedResults = resultsToRefresh.map(({ result }) => result)
21
+ const staleResults = previousCurrent ? [...refreshedResults, previousCurrent] : refreshedResults
22
+ const decorationsToRemove = findDecorations(decorations, staleResults, textblocks)
23
+ const decorationsToAdd = resultsToRefresh.map(({ result, index }) => {
24
+ return createDecoration(result, currentIndex, index)
25
+ })
26
+
27
+ return decorations.remove(decorationsToRemove).add(doc, decorationsToAdd)
28
+ }
@@ -0,0 +1,47 @@
1
+ import type { Node } from '@tiptap/pm/model'
2
+ import type { Mapping } from '@tiptap/pm/transform'
3
+
4
+ import type { FindAndReplaceMeta, FindAndReplacePluginState } from '../plugin/plugin-state.js'
5
+ import { searchTextblocks } from '../search/search.js'
6
+
7
+ import { getChangedTextblocks } from './getChangedTextblocks.js'
8
+ import { mapResult } from './mapResult.js'
9
+ import { mapResults } from './mapResults.js'
10
+ import { mergeResults } from './mergeResults.js'
11
+ import { refreshDecorations } from './refreshDecorations.js'
12
+ import { resolveCurrentIndex } from './resolveCurrentIndex.js'
13
+ import { resultInTextblocks } from './resultInTextblocks.js'
14
+
15
+ export function refreshDocumentState(
16
+ previousState: FindAndReplacePluginState,
17
+ state: FindAndReplacePluginState,
18
+ doc: Node,
19
+ meta: FindAndReplaceMeta | undefined,
20
+ mapping: Mapping,
21
+ ): FindAndReplacePluginState {
22
+ const textblocks = getChangedTextblocks(doc, mapping)
23
+ const mappedResults = mapResults(previousState.results, mapping)
24
+ const unaffectedResults = mappedResults.filter(result => !resultInTextblocks(result, textblocks))
25
+ const changedResults = searchTextblocks(textblocks, state.searchTerm, state)
26
+ const results = mergeResults(unaffectedResults, changedResults)
27
+ const currentIndex = resolveCurrentIndex(previousState, results, meta, true, mapping)
28
+ const previousCurrent =
29
+ previousState.currentIndex === null
30
+ ? null
31
+ : mapResult(previousState.results[previousState.currentIndex], mapping)
32
+ const decorations = previousState.decorations.map(mapping, doc)
33
+
34
+ return {
35
+ ...state,
36
+ results,
37
+ currentIndex,
38
+ decorations: refreshDecorations(
39
+ decorations,
40
+ doc,
41
+ results,
42
+ currentIndex,
43
+ textblocks,
44
+ previousCurrent,
45
+ ),
46
+ }
47
+ }
@@ -0,0 +1,27 @@
1
+ import type { Node } from '@tiptap/pm/model'
2
+ import type { Mapping } from '@tiptap/pm/transform'
3
+
4
+ import type { FindAndReplaceMeta, FindAndReplacePluginState } from '../plugin/plugin-state.js'
5
+ import { searchDocument } from '../search/search.js'
6
+
7
+ import { createDecorations } from './createDecorations.js'
8
+ import { resolveCurrentIndex } from './resolveCurrentIndex.js'
9
+
10
+ export function refreshState(
11
+ previousState: FindAndReplacePluginState,
12
+ state: FindAndReplacePluginState,
13
+ doc: Node,
14
+ meta: FindAndReplaceMeta | undefined,
15
+ docChanged: boolean,
16
+ mapping: Mapping | undefined,
17
+ ): FindAndReplacePluginState {
18
+ const results = searchDocument(doc, state.searchTerm, state)
19
+ const currentIndex = resolveCurrentIndex(previousState, results, meta, docChanged, mapping)
20
+
21
+ return {
22
+ ...state,
23
+ results,
24
+ currentIndex,
25
+ decorations: createDecorations(doc, results, currentIndex),
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ import type { EditorState, Transaction } from '@tiptap/pm/state'
2
+
3
+ import type { SearchResult } from '../search/search.js'
4
+ import { groupResults } from './groupResults.js'
5
+ import { replaceGroup } from './replaceGroup.js'
6
+
7
+ export function replaceAllResults(
8
+ tr: Transaction,
9
+ state: EditorState,
10
+ results: SearchResult[],
11
+ replaceTerm: string,
12
+ ): void {
13
+ const groups = groupResults(state, results)
14
+
15
+ for (const group of groups.reverse()) {
16
+ replaceGroup(tr, group, replaceTerm)
17
+ }
18
+ }
@@ -0,0 +1,54 @@
1
+ import type { Transaction } from '@tiptap/pm/state'
2
+
3
+ import type { SearchResult } from '../search/search.js'
4
+ import type { ResultGroup } from './groupResults.js'
5
+
6
+ function replaceSeparateResults(
7
+ tr: Transaction,
8
+ results: SearchResult[],
9
+ replaceTerm: string,
10
+ ): void {
11
+ for (const result of [...results].reverse()) {
12
+ tr.insertText(replaceTerm, result.from, result.to)
13
+ }
14
+ }
15
+
16
+ function createReplacement(
17
+ group: ResultGroup,
18
+ text: string,
19
+ firstResult: SearchResult,
20
+ replaceTerm: string,
21
+ ): string {
22
+ let offset = firstResult.from - group.from
23
+ const replacement: string[] = []
24
+
25
+ for (const result of group.results) {
26
+ const from = result.from - group.from
27
+ const to = result.to - group.from
28
+
29
+ replacement.push(text.slice(offset, from), replaceTerm)
30
+ offset = to
31
+ }
32
+
33
+ return replacement.join('')
34
+ }
35
+
36
+ export function replaceGroup(tr: Transaction, group: ResultGroup, replaceTerm: string): void {
37
+ const firstResult = group.results[0]
38
+ const lastResult = group.results.at(-1)
39
+
40
+ if (!lastResult) {
41
+ return
42
+ }
43
+
44
+ if (group.text === null) {
45
+ replaceSeparateResults(tr, group.results, replaceTerm)
46
+ return
47
+ }
48
+
49
+ tr.insertText(
50
+ createReplacement(group, group.text, firstResult, replaceTerm),
51
+ firstResult.from,
52
+ lastResult.to,
53
+ )
54
+ }
@@ -0,0 +1,27 @@
1
+ import type { Mapping } from '@tiptap/pm/transform'
2
+
3
+ import type { FindAndReplaceMeta, FindAndReplacePluginState } from '../plugin/plugin-state.js'
4
+ import type { SearchResult } from '../search/search.js'
5
+
6
+ import { getMetaCurrentIndex } from './getMetaCurrentIndex.js'
7
+ import { getSearchCurrentIndex } from './getSearchCurrentIndex.js'
8
+
9
+ export function resolveCurrentIndex(
10
+ previousState: FindAndReplacePluginState,
11
+ results: SearchResult[],
12
+ meta: FindAndReplaceMeta | undefined,
13
+ docChanged: boolean,
14
+ mapping: Mapping | undefined,
15
+ ): number | null {
16
+ if (results.length === 0) {
17
+ return null
18
+ }
19
+
20
+ const metaIndex = getMetaCurrentIndex(meta, results.length)
21
+
22
+ if (metaIndex !== undefined) {
23
+ return metaIndex
24
+ }
25
+
26
+ return getSearchCurrentIndex(previousState, results, meta, docChanged, mapping)
27
+ }
@@ -0,0 +1,30 @@
1
+ import type { SearchResult } from '../search/search.js'
2
+
3
+ import type { TextblockRange } from './types.js'
4
+
5
+ export function resultInTextblocks(
6
+ result: SearchResult,
7
+ textblocks: readonly TextblockRange[],
8
+ ): boolean {
9
+ let start = 0
10
+ let end = textblocks.length - 1
11
+
12
+ while (start <= end) {
13
+ const index = Math.floor((start + end) / 2)
14
+ const textblock = textblocks[index]
15
+
16
+ if (result.from < textblock.from) {
17
+ end = index - 1
18
+ continue
19
+ }
20
+
21
+ if (result.to > textblock.to) {
22
+ start = index + 1
23
+ continue
24
+ }
25
+
26
+ return true
27
+ }
28
+
29
+ return false
30
+ }
@@ -0,0 +1,5 @@
1
+ import type { SearchResult } from '../search/search.js'
2
+
3
+ export function sameResult(first: SearchResult, second: SearchResult): boolean {
4
+ return first.from === second.from && first.to === second.to
5
+ }
@@ -0,0 +1,18 @@
1
+ import type { SearchResult } from '../search/search.js'
2
+
3
+ import { isCurrentResult } from './isCurrentResult.js'
4
+ import { resultInTextblocks } from './resultInTextblocks.js'
5
+ import type { TextblockRange } from './types.js'
6
+
7
+ export function shouldRefreshResult(
8
+ result: SearchResult,
9
+ textblocks: readonly TextblockRange[],
10
+ previousCurrent: SearchResult | null,
11
+ current: SearchResult | null,
12
+ ): boolean {
13
+ return (
14
+ resultInTextblocks(result, textblocks) ||
15
+ isCurrentResult(result, previousCurrent) ||
16
+ isCurrentResult(result, current)
17
+ )
18
+ }
@@ -0,0 +1,7 @@
1
+ import type { FindAndReplaceMeta } from '../plugin/plugin-state.js'
2
+
3
+ const searchKeys = ['searchTerm', 'caseSensitive', 'useRegex', 'wholeWord'] as const
4
+
5
+ export function touchesSearch(meta: FindAndReplaceMeta | undefined): boolean {
6
+ return !!meta && searchKeys.some(key => key in meta)
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { TextblockSearchTarget } from '../search/search.js'
2
+
3
+ export interface TextblockRange extends TextblockSearchTarget {
4
+ from: number
5
+ to: number
6
+ }
7
+
8
+ export interface IndexedResult {
9
+ result: import('../search/search.js').SearchResult
10
+ index: number
11
+ }
@@ -0,0 +1,33 @@
1
+ import type { Node } from '@tiptap/pm/model'
2
+ import type { Mapping } from '@tiptap/pm/transform'
3
+
4
+ import type { FindAndReplaceMeta, FindAndReplacePluginState } from '../plugin/plugin-state.js'
5
+
6
+ import { hasCurrentIndex } from './hasCurrentIndex.js'
7
+ import { refreshCurrentIndexState } from './refreshCurrentIndexState.js'
8
+ import { refreshDocumentState } from './refreshDocumentState.js'
9
+ import { refreshState } from './refreshState.js'
10
+ import { touchesSearch } from './touchesSearch.js'
11
+
12
+ export function updateState(
13
+ previousState: FindAndReplacePluginState,
14
+ state: FindAndReplacePluginState,
15
+ doc: Node,
16
+ meta: FindAndReplaceMeta | undefined,
17
+ docChanged: boolean,
18
+ mapping: Mapping,
19
+ ): FindAndReplacePluginState {
20
+ if (touchesSearch(meta)) {
21
+ return refreshState(previousState, state, doc, meta, docChanged, mapping)
22
+ }
23
+
24
+ if (docChanged) {
25
+ return refreshDocumentState(previousState, state, doc, meta, mapping)
26
+ }
27
+
28
+ if (hasCurrentIndex(meta)) {
29
+ return refreshCurrentIndexState(previousState, state, doc, meta)
30
+ }
31
+
32
+ return state
33
+ }