search-snapshot.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { Text } from '@codemirror/state'
  2. import { RegExpCursor, SearchCursor, SearchQuery } from '@codemirror/search'
  3. import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
  4. import { categorizer, regexpWordTest, stringWordTest } from './search'
  5. import { sendSearchEvent } from '@/features/event-tracking/search-events'
  6. import { populateEditorRedesignSegmentation } from '@/shared/hooks/use-editor-analytics'
  7. export type Hit = {
  8. lineIndex: number
  9. matchIndex: number
  10. length: number
  11. }
  12. export type MatchedFile = {
  13. path: string
  14. lines: string[]
  15. hits: Hit[]
  16. }
  17. const toLowerCase = (string: string) => string.toLowerCase()
  18. export const searchSnapshot = async (
  19. projectSnapshot: ProjectSnapshot,
  20. searchQuery: SearchQuery
  21. ) => {
  22. if (!searchQuery.search.trim().length) {
  23. return
  24. }
  25. const matchedFiles = new Map<string, MatchedFile>()
  26. const createCursor = (text: Text) => {
  27. if (searchQuery.regexp) {
  28. return new RegExpCursor(text, searchQuery.search, {
  29. ignoreCase: !searchQuery.caseSensitive,
  30. test: searchQuery.wholeWord ? regexpWordTest(categorizer) : undefined,
  31. })
  32. }
  33. return new SearchCursor(
  34. text,
  35. searchQuery.search,
  36. undefined,
  37. undefined,
  38. searchQuery.caseSensitive ? undefined : toLowerCase,
  39. searchQuery.wholeWord ? stringWordTest(text, categorizer) : undefined
  40. )
  41. }
  42. const docPaths = projectSnapshot.getDocPaths()
  43. for (const path of docPaths) {
  44. const content = projectSnapshot.getDocContents(path)
  45. if (content) {
  46. const lines = content.split('\n')
  47. const text = Text.of(lines)
  48. const cursor = createCursor(text)
  49. while (!cursor.next().done) {
  50. const { from, to } = cursor.value
  51. const matchedFile: MatchedFile = matchedFiles.get(path) ?? {
  52. path,
  53. lines,
  54. hits: [],
  55. }
  56. const line = text.lineAt(from)
  57. matchedFile.hits.push({
  58. lineIndex: line.number - 1,
  59. matchIndex: from - line.from,
  60. length: to - from,
  61. })
  62. matchedFiles.set(path, matchedFile)
  63. }
  64. }
  65. }
  66. const results = [...matchedFiles.values()].sort((a, b) =>
  67. a.path.localeCompare(b.path)
  68. )
  69. sendSearchEvent(
  70. 'search-execute',
  71. populateEditorRedesignSegmentation({
  72. searchType: 'full-project',
  73. totalDocs: docPaths.length,
  74. totalResults: results.flatMap(file => file.hits).length,
  75. })
  76. )
  77. return results
  78. }