open-documents.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. // Migrated from static methods of Document in Document.js
  2. import { DocumentContainer } from '@/features/ide-react/editor/document-container'
  3. import { debugConsole } from '@/utils/debugging'
  4. import { Socket } from '@/features/ide-react/connection/types/socket'
  5. import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
  6. import EditorWatchdogManager from '@/features/ide-react/connection/editor-watchdog-manager'
  7. export class OpenDocuments {
  8. private openDocs = new Map<string, DocumentContainer>()
  9. // eslint-disable-next-line no-useless-constructor
  10. constructor(
  11. private readonly socket: Socket,
  12. private readonly globalEditorWatchdogManager: EditorWatchdogManager,
  13. private readonly events: IdeEventEmitter
  14. ) {}
  15. getDocument(docId: string) {
  16. // Try to clean up existing docs before reopening them. If the doc has no
  17. // buffered ops then it will be deleted by _cleanup() and a new instance
  18. // of the document created below. This prevents us trying to follow the
  19. // joinDoc:existing code path on an existing doc that doesn't have any
  20. // local changes and getting an error if its version is too old.
  21. if (this.openDocs.has(docId)) {
  22. debugConsole.log(
  23. `[getDocument] Cleaning up existing document instance for ${docId}`
  24. )
  25. this.openDocs.get(docId)?.cleanUp()
  26. }
  27. if (!this.openDocs.has(docId)) {
  28. debugConsole.log(
  29. `[getDocument] Creating new document instance for ${docId}`
  30. )
  31. this.createDoc(docId)
  32. } else {
  33. debugConsole.log(
  34. `[getDocument] Returning existing document instance for ${docId}`
  35. )
  36. }
  37. return this.openDocs.get(docId)
  38. }
  39. private createDoc(docId: string) {
  40. const doc = new DocumentContainer(
  41. docId,
  42. this.socket,
  43. this.globalEditorWatchdogManager,
  44. this.events,
  45. this.detachDoc.bind(this)
  46. )
  47. this.openDocs.set(docId, doc)
  48. }
  49. detachDoc(docId: string, doc: DocumentContainer) {
  50. if (this.openDocs.get(docId) === doc) {
  51. debugConsole.log(
  52. `[detach] Removing document with ID (${docId}) from openDocs`
  53. )
  54. this.openDocs.delete(docId)
  55. } else {
  56. // It's possible that this instance has error, and the doc has been reloaded.
  57. // This creates a new instance in Document.openDoc with the same id. We shouldn't
  58. // clear it because it's not this instance.
  59. debugConsole.log(
  60. `[_cleanUp] New instance of (${docId}) created. Not removing`
  61. )
  62. }
  63. }
  64. hasUnsavedChanges() {
  65. for (const doc of this.openDocs.values()) {
  66. if (doc.hasBufferedOps()) {
  67. return true
  68. }
  69. }
  70. return false
  71. }
  72. flushAll() {
  73. for (const doc of this.openDocs.values()) {
  74. doc.flush()
  75. }
  76. }
  77. unsavedDocIds() {
  78. const ids = []
  79. for (const [docId, doc] of this.openDocs) {
  80. if (!doc.pollSavedStatus()) {
  81. ids.push(docId)
  82. }
  83. }
  84. return ids
  85. }
  86. async awaitBufferedOps(signal: AbortSignal) {
  87. if (this.hasUnsavedChanges()) {
  88. const { promise, resolve } = Promise.withResolvers<void>()
  89. let resolved = false
  90. const listener = () => {
  91. if (!this.hasUnsavedChanges()) {
  92. debugConsole.log('saved')
  93. window.removeEventListener('doc:saved', listener)
  94. resolved = true
  95. resolve()
  96. }
  97. }
  98. window.addEventListener('doc:saved', listener)
  99. signal.addEventListener('abort', () => {
  100. if (!resolved) {
  101. debugConsole.log('aborted')
  102. window.removeEventListener('doc:saved', listener)
  103. resolve()
  104. }
  105. })
  106. this.flushAll()
  107. await promise
  108. }
  109. }
  110. }