cache.ts 2.6 KB

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