cache.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import { StateField, StateEffect } from '@codemirror/state'
  2. import LRU from 'lru-cache'
  3. import { Word } from './spellchecker'
  4. const CACHE_MAX = 15000
  5. export const cacheKey = (lang: string, wordText: string) => {
  6. return `${lang}:${wordText}`
  7. }
  8. export type WordCacheValue = string[] | boolean | number
  9. export class WordCache {
  10. private _cache: LRU<string, WordCacheValue>
  11. constructor() {
  12. this._cache = new LRU({ max: CACHE_MAX })
  13. }
  14. set(lang: string, wordText: string, value: WordCacheValue) {
  15. const key = cacheKey(lang, wordText)
  16. this._cache.set(key, value)
  17. }
  18. get(lang: string, wordText: string) {
  19. const key = cacheKey(lang, wordText)
  20. return this._cache.get(key)
  21. }
  22. remove(lang: string, wordText: string) {
  23. const key = cacheKey(lang, wordText)
  24. this._cache.delete(key)
  25. }
  26. /*
  27. * Given a language and a list of words,
  28. * check the cache and sort the words into two categories:
  29. * - words we know to be misspelled
  30. * - words that are presently unknown to us
  31. */
  32. checkWords(
  33. lang: string,
  34. wordsToCheck: Word[]
  35. ): {
  36. knownMisspelledWords: Word[]
  37. unknownWords: Word[]
  38. } {
  39. const knownMisspelledWords = []
  40. const unknownWords = []
  41. const seen: Record<string, WordCacheValue | undefined> = {}
  42. for (const word of wordsToCheck) {
  43. const wordText = word.text
  44. if (seen[wordText] == null) {
  45. seen[wordText] = this.get(lang, wordText)
  46. }
  47. const cached = seen[wordText]
  48. if (cached == null) {
  49. // Word is not known
  50. unknownWords.push(word)
  51. } else if (cached === true) {
  52. // Word is known to be correct
  53. } else {
  54. // Word is known to be misspelled
  55. word.suggestions = cached
  56. knownMisspelledWords.push(word)
  57. }
  58. }
  59. return {
  60. knownMisspelledWords,
  61. unknownWords,
  62. }
  63. }
  64. }
  65. export const addWordToCache = StateEffect.define<{
  66. lang: string
  67. wordText: string
  68. value: string[] | boolean
  69. }>()
  70. export const removeWordFromCache = StateEffect.define<{
  71. lang: string
  72. wordText: string
  73. }>()
  74. // Share a single instance of WordCache between all instances of the CM6 source editor. This means that cached words are
  75. // retained when switching away from CM6 and then back to it.
  76. const wordCache = new WordCache()
  77. export const cacheField = StateField.define<WordCache>({
  78. create() {
  79. return wordCache
  80. },
  81. update(cache, transaction) {
  82. for (const effect of transaction.effects) {
  83. if (effect.is(addWordToCache)) {
  84. const { lang, wordText, value } = effect.value
  85. cache.set(lang, wordText, value)
  86. } else if (effect.is(removeWordFromCache)) {
  87. const { lang, wordText } = effect.value
  88. cache.remove(lang, wordText)
  89. }
  90. }
  91. return cache
  92. },
  93. })