@@ -287,7 +287,7 @@ const show_diff_with_actions = async (
287287
288288export const review = async ( params : {
289289 original_states : OriginalFileState [ ]
290- view_provider ? : ViewProvider
290+ view_provider : ViewProvider
291291 raw_instructions ?: string
292292} ) : Promise < {
293293 accepted_files : ReviewableFile [ ]
@@ -314,11 +314,9 @@ export const review = async (params: {
314314
315315 const text_document_change_listener =
316316 vscode . workspace . onDidChangeTextDocument ( ( event ) => {
317- if ( ! params . view_provider ) return
318-
319317 const changed_doc_path = event . document . uri . fsPath
320318 const changed_file_in_review = prepared_files . find (
321- ( pf ) => pf . sanitized_path === changed_doc_path
319+ ( pf ) => pf . sanitized_path == changed_doc_path
322320 )
323321
324322 if ( changed_file_in_review ) {
@@ -339,9 +337,244 @@ export const review = async (params: {
339337 command : 'UPDATE_FILE_IN_REVIEW' ,
340338 file : changed_file_in_review . reviewable_file
341339 } )
340+ } else {
341+ // A file not in the review has been changed. Add it to the review.
342+ const doc = event . document
343+ if ( doc . uri . scheme != 'file' ) return
344+ const workspace_folder = vscode . workspace . getWorkspaceFolder ( doc . uri )
345+ if ( ! workspace_folder ) return
346+
347+ // Check if it's already added to avoid races
348+ if ( prepared_files . some ( ( pf ) => pf . sanitized_path == doc . uri . fsPath ) ) {
349+ return
350+ }
351+
352+ const new_content = doc . getText ( )
353+ const relative_path = vscode . workspace
354+ . asRelativePath ( doc . uri , false )
355+ . replace ( / \\ / g, '/' )
356+
357+ let original_content = ''
358+ let is_new = false
359+ if ( doc . isUntitled ) {
360+ is_new = true
361+ } else {
362+ try {
363+ original_content = fs . readFileSync ( doc . uri . fsPath , 'utf8' )
364+ } catch ( e ) {
365+ is_new = true // File on disk does not exist, but buffer does.
366+ }
367+ }
368+
369+ const new_original_state : OriginalFileState = {
370+ file_path : relative_path ,
371+ content : original_content ,
372+ is_new : is_new ,
373+ workspace_name : workspace_folder . name
374+ }
375+
376+ const diff_stats = get_diff_stats ( {
377+ original_content : original_content ,
378+ new_content : new_content
379+ } )
380+
381+ const sanitized_file_path = doc . uri . fsPath
382+ const hash = crypto
383+ . createHash ( 'md5' )
384+ . update ( sanitized_file_path )
385+ . digest ( 'hex' )
386+ const temp_filename = `cwc-review-${ hash } .tmp`
387+ const temp_file_path = path . join ( os . tmpdir ( ) , temp_filename )
388+ const is_deleted =
389+ ! is_new && new_content == '' && original_content != ''
390+
391+ const reviewable_file : ReviewableFile = {
392+ file_path : relative_path ,
393+ content : new_content ,
394+ workspace_name : workspace_folder . name ,
395+ is_new : is_new ,
396+ is_deleted : is_deleted ,
397+ lines_added : diff_stats . lines_added ,
398+ lines_removed : diff_stats . lines_removed
399+ }
400+
401+ const new_prepared_file : PreparedFile = {
402+ reviewable_file,
403+ sanitized_path : sanitized_file_path ,
404+ original_content : original_content ,
405+ temp_file_path,
406+ file_exists : ! is_new
407+ }
408+
409+ params . original_states . push ( new_original_state )
410+ prepared_files . push ( new_prepared_file )
411+ create_temp_files_with_original_content ( [ new_prepared_file ] )
412+ params . view_provider . send_message ( {
413+ command : 'UPDATE_FILE_IN_REVIEW' ,
414+ file : new_prepared_file . reviewable_file
415+ } )
342416 }
343417 } )
344418
419+ const file_delete_listener = vscode . workspace . onDidDeleteFiles ( ( event ) => {
420+ for ( const uri of event . files ) {
421+ if ( uri . scheme != 'file' ) continue
422+
423+ const deleted_file_path = uri . fsPath
424+ const deleted_file_in_review = prepared_files . find (
425+ ( pf ) => pf . sanitized_path == deleted_file_path
426+ )
427+
428+ if ( ! deleted_file_in_review ) {
429+ const workspace_folder = vscode . workspace . getWorkspaceFolder ( uri )
430+ if ( ! workspace_folder ) continue
431+
432+ if ( prepared_files . some ( ( pf ) => pf . sanitized_path == uri . fsPath ) ) {
433+ continue
434+ }
435+
436+ const new_content = ''
437+ const relative_path = vscode . workspace
438+ . asRelativePath ( uri , false )
439+ . replace ( / \\ / g, '/' )
440+
441+ const original_content_for_undo = '' // Limitation: cannot get content of deleted file
442+
443+ const new_original_state : OriginalFileState = {
444+ file_path : relative_path ,
445+ content : original_content_for_undo ,
446+ is_new : false ,
447+ workspace_name : workspace_folder . name
448+ }
449+
450+ const diff_stats = get_diff_stats ( {
451+ original_content : original_content_for_undo ,
452+ new_content : new_content
453+ } )
454+
455+ const sanitized_file_path = uri . fsPath
456+ const hash = crypto
457+ . createHash ( 'md5' )
458+ . update ( sanitized_file_path )
459+ . digest ( 'hex' )
460+ const temp_filename = `cwc-review-${ hash } .tmp`
461+ const temp_file_path = path . join ( os . tmpdir ( ) , temp_filename )
462+
463+ const reviewable_file : ReviewableFile = {
464+ file_path : relative_path ,
465+ content : new_content ,
466+ workspace_name : workspace_folder . name ,
467+ is_new : false ,
468+ is_deleted : true ,
469+ lines_added : diff_stats . lines_added ,
470+ lines_removed : diff_stats . lines_removed
471+ }
472+
473+ const new_prepared_file : PreparedFile = {
474+ reviewable_file,
475+ sanitized_path : sanitized_file_path ,
476+ original_content : original_content_for_undo ,
477+ temp_file_path,
478+ file_exists : false
479+ }
480+
481+ params . original_states . push ( new_original_state )
482+ prepared_files . push ( new_prepared_file )
483+ create_temp_files_with_original_content ( [ new_prepared_file ] )
484+ params . view_provider . send_message ( {
485+ command : 'UPDATE_FILE_IN_REVIEW' ,
486+ file : new_prepared_file . reviewable_file
487+ } )
488+ } else {
489+ deleted_file_in_review . reviewable_file . is_deleted = true
490+ deleted_file_in_review . reviewable_file . content = ''
491+ params . view_provider . send_message ( {
492+ command : 'UPDATE_FILE_IN_REVIEW' ,
493+ file : deleted_file_in_review . reviewable_file
494+ } )
495+ }
496+ }
497+ } )
498+
499+ const file_created_listener = vscode . workspace . onDidCreateFiles ( ( event ) => {
500+ for ( const uri of event . files ) {
501+ if ( uri . scheme !== 'file' ) continue
502+
503+ const workspace_folder = vscode . workspace . getWorkspaceFolder ( uri )
504+ if ( ! workspace_folder ) continue
505+
506+ // Avoid duplicates if already tracked
507+ if ( prepared_files . some ( ( pf ) => pf . sanitized_path === uri . fsPath ) ) {
508+ continue
509+ }
510+
511+ // Read the content of the newly created file (may be empty)
512+ let new_content = ''
513+ try {
514+ new_content = fs . readFileSync ( uri . fsPath , 'utf8' )
515+ } catch ( e ) {
516+ new_content = ''
517+ }
518+
519+ const relative_path = vscode . workspace
520+ . asRelativePath ( uri , false )
521+ . replace ( / \\ / g, '/' )
522+
523+ const original_content = '' // New file had no previous content
524+ const is_new = true
525+
526+ const new_original_state : OriginalFileState = {
527+ file_path : relative_path ,
528+ content : original_content ,
529+ is_new : is_new ,
530+ workspace_name : workspace_folder . name
531+ }
532+
533+ const diff_stats = get_diff_stats ( {
534+ original_content : original_content ,
535+ new_content : new_content
536+ } )
537+
538+ const sanitized_file_path = uri . fsPath
539+ const hash = crypto
540+ . createHash ( 'md5' )
541+ . update ( sanitized_file_path )
542+ . digest ( 'hex' )
543+ const temp_filename = `cwc-review-${ hash } .tmp`
544+ const temp_file_path = path . join ( os . tmpdir ( ) , temp_filename )
545+
546+ const reviewable_file : ReviewableFile = {
547+ file_path : relative_path ,
548+ content : new_content ,
549+ workspace_name : workspace_folder . name ,
550+ is_new : true ,
551+ is_deleted : false ,
552+ lines_added : diff_stats . lines_added ,
553+ lines_removed : diff_stats . lines_removed
554+ }
555+
556+ const new_prepared_file : PreparedFile = {
557+ reviewable_file,
558+ sanitized_path : sanitized_file_path ,
559+ original_content : original_content ,
560+ temp_file_path,
561+ file_exists : false
562+ }
563+
564+ params . original_states . push ( new_original_state )
565+ prepared_files . push ( new_prepared_file )
566+
567+ // Create temp file with the original (empty) content for diff view
568+ create_temp_files_with_original_content ( [ new_prepared_file ] )
569+
570+ // Notify the panel to include this file in the review UI
571+ params . view_provider . send_message ( {
572+ command : 'UPDATE_FILE_IN_REVIEW' ,
573+ file : new_prepared_file . reviewable_file
574+ } )
575+ }
576+ } )
577+
345578 try {
346579 prepared_files = await prepare_files_from_original_states ( {
347580 original_states : params . original_states ,
@@ -474,15 +707,32 @@ export const review = async (params: {
474707 )
475708 )
476709
477- review_items . forEach ( ( item ) => {
478- const file_identifier = `${
479- item . file . reviewable_file . workspace_name || ''
480- } :${ item . file . reviewable_file . file_path } `
481- item . status = accepted_file_identifiers . has ( file_identifier )
482- ? 'accepted'
483- : 'rejected'
484- } )
485- break
710+ const accepted_files = prepared_files
711+ . filter ( ( pf ) => {
712+ const identifier = `${ pf . reviewable_file . workspace_name || '' } :${
713+ pf . reviewable_file . file_path
714+ } `
715+ return accepted_file_identifiers . has ( identifier )
716+ } )
717+ . map ( ( pf ) => pf . reviewable_file )
718+
719+ const rejected_states = prepared_files
720+ . filter ( ( pf ) => {
721+ const identifier = `${ pf . reviewable_file . workspace_name || '' } :${
722+ pf . reviewable_file . file_path
723+ } `
724+ return ! accepted_file_identifiers . has ( identifier )
725+ } )
726+ . map ( ( item ) => {
727+ return params . original_states . find (
728+ ( state ) =>
729+ state . file_path == item . reviewable_file . file_path &&
730+ state . workspace_name == item . reviewable_file . workspace_name
731+ )
732+ } )
733+ . filter ( ( state ) : state is OriginalFileState => state !== undefined )
734+
735+ return { accepted_files, rejected_states }
486736 }
487737
488738 if ( 'jump_to' in decision ) {
@@ -526,6 +776,8 @@ export const review = async (params: {
526776 return { accepted_files, rejected_states }
527777 } finally {
528778 text_document_change_listener . dispose ( )
779+ file_delete_listener . dispose ( )
780+ file_created_listener . dispose ( )
529781 await close_review_diff_editors ( prepared_files )
530782 cleanup_temp_files ( prepared_files )
531783 toggle_file_review_state = undefined
0 commit comments