1717
1818class RDoc ::Server
1919
20- LIVE_RELOAD_SCRIPT = <<~JS
21- <script>
22- (function() {
23- var lastChange = 0;
24- setInterval(function() {
25- fetch('/__status').then(function(r) { return r.json(); }).then(function(data) {
26- if (lastChange && data.last_change > lastChange) location.reload();
27- lastChange = data.last_change;
28- }).catch(function() {});
29- }, 1000);
30- })();
31- </script>
32- JS
20+ ##
21+ # Returns a live-reload polling script with the given +last_change_time+
22+ # embedded so the browser knows the exact timestamp of the content it
23+ # received. This avoids a race where a change that occurs between page
24+ # generation and the first poll would be silently skipped.
25+
26+ def self . live_reload_script ( last_change_time )
27+ <<~JS
28+ <script>
29+ (function() {
30+ var lastChange = #{ last_change_time } ;
31+ setInterval(function() {
32+ fetch('/__status').then(function(r) { return r.json(); }).then(function(data) {
33+ if (data.last_change > lastChange) location.reload();
34+ lastChange = data.last_change;
35+ }).catch(function() {});
36+ }, 1000);
37+ })();
38+ </script>
39+ JS
40+ end
3341
3442 CONTENT_TYPES = {
3543 '.html' => 'text/html' ,
@@ -216,7 +224,8 @@ def serve_page(path)
216224 not_found = @generator . generate_servlet_not_found (
217225 "The page <kbd>#{ ERB ::Util . html_escape path } </kbd> was not found"
218226 )
219- return [ 404 , 'text/html' , inject_live_reload ( not_found || '' ) ]
227+ t = @mutex . synchronize { @last_change_time }
228+ return [ 404 , 'text/html' , inject_live_reload ( not_found || '' , t ) ]
220229 end
221230
222231 ext = File . extname ( name )
@@ -234,7 +243,7 @@ def render_page(name)
234243 result = generate_page ( name )
235244 return nil unless result
236245
237- result = inject_live_reload ( result ) if name . end_with? ( '.html' )
246+ result = inject_live_reload ( result , @last_change_time ) if name . end_with? ( '.html' )
238247 @page_cache [ name ] = result
239248 end
240249 end
@@ -273,8 +282,8 @@ def build_search_index
273282 ##
274283 # Injects the live-reload polling script before +</body>+.
275284
276- def inject_live_reload ( html )
277- html . sub ( '</body>' , "#{ LIVE_RELOAD_SCRIPT } </body>" )
285+ def inject_live_reload ( html , last_change_time )
286+ html . sub ( '</body>' , "#{ self . class . live_reload_script ( last_change_time ) } </body>" )
278287 end
279288
280289 ##
@@ -374,6 +383,7 @@ def reparse_and_refresh(changed_files, removed_files)
374383 removed_files . each do |f |
375384 @file_mtimes . delete ( f )
376385 relative = relative_path_for ( f )
386+ clear_file_contributions ( relative )
377387 @store . remove_file ( relative )
378388 end
379389 end
@@ -402,9 +412,10 @@ def reparse_and_refresh(changed_files, removed_files)
402412
403413 ##
404414 # Removes a file's contributions (methods, constants, comments, etc.)
405- # from its classes and modules without removing the classes themselves
406- # from the store. This prevents duplication when the file is re-parsed
407- # while preserving shared namespaces like +RDoc+ that span many files.
415+ # from its classes and modules. If no other files contribute to a
416+ # class or module, it is removed from the store entirely. This
417+ # prevents duplication when the file is re-parsed while preserving
418+ # shared namespaces like +RDoc+ that span many files.
408419
409420 def clear_file_contributions ( relative_name )
410421 top_level = @store . files_hash [ relative_name ]
@@ -442,6 +453,20 @@ def clear_file_contributions(relative_name)
442453
443454 # Remove this file from the class/module's file list
444455 cm . in_files . delete ( top_level )
456+
457+ # If no files contribute to this class/module anymore, remove it
458+ # from the store entirely. This handles file deletion correctly
459+ # for classes that are only defined in the deleted file, while
460+ # preserving classes that span multiple files.
461+ if cm . in_files . empty?
462+ if cm . is_a? ( RDoc ::NormalModule )
463+ @store . modules_hash . delete ( cm . full_name )
464+ else
465+ @store . classes_hash . delete ( cm . full_name )
466+ end
467+ cm . parent &.classes_hash &.delete ( cm . name )
468+ cm . parent &.modules_hash &.delete ( cm . name )
469+ end
445470 end
446471
447472 # Clear the TopLevel's class/module list to prevent duplicates
0 commit comments