@@ -13,6 +13,8 @@ import fileTypeHandler from "./fileTypeHandler";
1313import recents from "./recents" ;
1414import appSettings from "./settings" ;
1515
16+ let loadingFileCount = 0 ;
17+
1618/**
1719 * @typedef {object } FileOptions
1820 * @property {string } text
@@ -24,6 +26,7 @@ import appSettings from "./settings";
2426 * @property {string } uri
2527 * @property {string } paneId
2628 * @property {boolean } persistInSession
29+ * @property {AbortSignal } signal Discard an obsolete open before activating its file.
2730 */
2831
2932/**
@@ -33,6 +36,9 @@ import appSettings from "./settings";
3336 */
3437
3538export default async function openFile ( file , options = { } ) {
39+ const { signal } = options ;
40+ if ( signal ?. aborted ) return ;
41+ let releaseTitleLoader ;
3642 try {
3743 let uri = typeof file === "string" ? file : file . uri ;
3844 if ( ! uri ) return ;
@@ -112,13 +118,15 @@ export default async function openFile(file, options = {}) {
112118 return ;
113119 }
114120
115- loader . showTitleLoader ( ) ;
121+ releaseTitleLoader = acquireTitleLoader ( signal ) ;
116122 const settings = appSettings . value ;
117123 const fs = fsOperation ( uri ) ;
118124 const fileInfo = await fs . stat ( ) ;
125+ if ( signal ?. aborted ) return ;
119126 const name = fileInfo . name || file . filename || uri ;
120127 const readOnly = fileInfo . canWrite === false ;
121128 const createEditor = ( isUnsaved , text , detectedEncoding ) => {
129+ if ( signal ?. aborted ) return ;
122130 new EditorFile ( name , {
123131 uri,
124132 text,
@@ -152,10 +160,12 @@ export default async function openFile(file, options = {}) {
152160 encoding,
153161 mode,
154162 createEditor,
163+ signal,
155164 } ,
156165 } ) ;
157166 return ;
158167 } catch ( error ) {
168+ if ( signal ?. aborted ) return ;
159169 console . error ( `File handler '${ customHandler . id } ' failed:` , error ) ;
160170 // Continue with default handling if custom handler fails
161171 }
@@ -173,6 +183,10 @@ export default async function openFile(file, options = {}) {
173183
174184 if ( videoRegex . test ( name ) ) {
175185 const objectUrl = await fileToDataUrl ( uri ) ;
186+ if ( signal ?. aborted ) {
187+ URL . revokeObjectURL ( objectUrl ) ;
188+ return ;
189+ }
176190 const videoContainer = (
177191 < div
178192 style = { {
@@ -212,6 +226,10 @@ export default async function openFile(file, options = {}) {
212226
213227 if ( imageRegex . test ( name ) ) {
214228 const objectUrl = await fileToDataUrl ( uri ) ;
229+ if ( signal ?. aborted ) {
230+ URL . revokeObjectURL ( objectUrl ) ;
231+ return ;
232+ }
215233 const imageContainer = (
216234 < div
217235 className = "image-container"
@@ -381,6 +399,10 @@ export default async function openFile(file, options = {}) {
381399
382400 if ( audioRegex . test ( name ) ) {
383401 const objectUrl = await fileToDataUrl ( uri ) ;
402+ if ( signal ?. aborted ) {
403+ URL . revokeObjectURL ( objectUrl ) ;
404+ return ;
405+ }
384406 const audioContainer = (
385407 < div
386408 style = { {
@@ -425,10 +447,11 @@ export default async function openFile(file, options = {}) {
425447
426448 if ( helpers . isBinary ( uri ) ) {
427449 const confirmation = await confirm ( strings . info , strings [ "binary file" ] ) ;
428- if ( ! confirmation ) return ;
450+ if ( ! confirmation || signal ?. aborted ) return ;
429451 }
430452
431453 const binData = await fs . readFile ( ) ;
454+ if ( signal ?. aborted ) return ;
432455
433456 // Determine encoding: if explicit provided use it, otherwise
434457 // if settings.defaultFileEncoding === 'auto' then detect; else use the default as-is
@@ -448,18 +471,36 @@ export default async function openFile(file, options = {}) {
448471 }
449472 }
450473
474+ if ( signal ?. aborted ) return ;
451475 const fileContent = await decode ( binData , detectedEncoding ) ;
476+ if ( signal ?. aborted ) return ;
452477
453478 createEditor ( false , fileContent , detectedEncoding ) ;
454479 if ( mode !== "single" ) recents . addFile ( uri ) ;
455480 return ;
456481 } catch ( error ) {
457- console . error ( error ) ;
482+ if ( ! signal ?. aborted ) console . error ( error ) ;
458483 } finally {
459- loader . removeTitleLoader ( ) ;
484+ releaseTitleLoader ?. ( ) ;
460485 }
461486}
462487
488+ /** Keep the shared indicator visible while any file open still needs it. */
489+ function acquireTitleLoader ( signal ) {
490+ if ( loadingFileCount ++ === 0 ) loader . showTitleLoader ( ) ;
491+ let released = false ;
492+ const release = ( ) => {
493+ // An aborted filesystem call may settle much later. Release immediately
494+ // on abort, and make its eventual finally block a no-op.
495+ if ( released ) return ;
496+ released = true ;
497+ signal ?. removeEventListener ( "abort" , release ) ;
498+ if ( -- loadingFileCount === 0 ) loader . removeTitleLoader ( ) ;
499+ } ;
500+ signal ?. addEventListener ( "abort" , release , { once : true } ) ;
501+ return release ;
502+ }
503+
463504/**
464505 * Converts file to data url
465506 * @param {string } file file url
0 commit comments