@@ -65,6 +65,8 @@ class CsvEditorController {
6565 private isUpdatingDocument = false ;
6666 private isSaving = false ;
6767 private pendingOperation : Promise < void > = Promise . resolve ( ) ;
68+ private isDisposed = false ;
69+ private defaultEditorHandoff : Promise < void > | undefined ;
6870 private currentWebviewPanel : vscode . WebviewPanel | undefined ;
6971 private document ! : vscode . TextDocument ;
7072 private separatorCache : { version : number ; configKey : string ; separator : string } | undefined ;
@@ -81,6 +83,24 @@ class CsvEditorController {
8183 _token : vscode . CancellationToken
8284 ) : Promise < void > {
8385 this . document = document ;
86+ this . currentWebviewPanel = webviewPanel ;
87+ let changeDocumentSubscription : vscode . Disposable | undefined ;
88+ let saveDocumentSubscription : vscode . Disposable | undefined ;
89+ // Remote file checks can outlive the preview tab that requested them.
90+ // Observe disposal before awaiting anything so a closed panel stays closed.
91+ webviewPanel . onDidDispose ( ( ) => {
92+ this . isDisposed = true ;
93+ changeDocumentSubscription ?. dispose ( ) ;
94+ saveDocumentSubscription ?. dispose ( ) ;
95+ CsvEditorProvider . editors = CsvEditorProvider . editors . filter ( ed => ed !== this ) ;
96+ if ( CsvEditorProvider . currentActive === this ) {
97+ CsvEditorProvider . currentActive = undefined ;
98+ }
99+ this . currentWebviewPanel = undefined ;
100+ } ) ;
101+ if ( _token . isCancellationRequested ) {
102+ return ;
103+ }
84104
85105 const config = vscode . workspace . getConfiguration ( 'csv' , this . document . uri ) ;
86106 if ( ! config . get < boolean > ( 'enabled' , true ) ) {
@@ -90,17 +110,20 @@ class CsvEditorController {
90110 }
91111
92112 const proceed = await this . confirmLargeFileOpen ( config , webviewPanel , _token ) ;
93- if ( ! proceed ) {
113+ if ( ! proceed || this . isDisposed || _token . isCancellationRequested ) {
114+ return ;
115+ }
116+ // csv.enabled may have changed while a remote stat or the size prompt was pending.
117+ if ( ! vscode . workspace . getConfiguration ( 'csv' , document . uri ) . get < boolean > ( 'enabled' , true ) ) {
118+ await this . openWithDefaultEditorAndClose ( webviewPanel , document . uri ) ;
94119 return ;
95120 }
96121
97- this . currentWebviewPanel = webviewPanel ;
98122 CsvEditorProvider . editors . push ( this ) ;
99123
100124 webviewPanel . webview . options = {
101125 enableScripts : true ,
102- // Use file path for compatibility with older VS Code types (no Uri.joinPath)
103- localResourceRoots : [ vscode . Uri . file ( path . join ( this . context . extensionPath , 'media' ) ) ]
126+ localResourceRoots : [ this . getMediaUri ( ) ]
104127 } ;
105128
106129 this . refreshDiffContext ( webviewPanel ) ;
@@ -124,6 +147,8 @@ class CsvEditorController {
124147 }
125148 e . webviewPanel . webview . postMessage ( { type : 'focus' } ) ;
126149 CsvEditorProvider . currentActive = this ;
150+ } else if ( CsvEditorProvider . currentActive === this ) {
151+ CsvEditorProvider . currentActive = undefined ;
127152 }
128153 } ) ;
129154
@@ -190,7 +215,7 @@ class CsvEditorController {
190215 }
191216 } ) ) ;
192217
193- const changeDocumentSubscription = vscode . workspace . onDidChangeTextDocument ( e => {
218+ changeDocumentSubscription = vscode . workspace . onDidChangeTextDocument ( e => {
194219 if (
195220 e . document . uri . toString ( ) === document . uri . toString ( ) &&
196221 ! this . isUpdatingDocument &&
@@ -200,20 +225,14 @@ class CsvEditorController {
200225 }
201226 } ) ;
202227
203- const saveDocumentSubscription = vscode . workspace . onWillSaveTextDocument ( e => {
228+ saveDocumentSubscription = vscode . workspace . onWillSaveTextDocument ( e => {
204229 if ( e . document . uri . toString ( ) === document . uri . toString ( ) && ! this . isSaving ) {
205230 // Native File > Save can run while the webview's blur edit is applying.
206231 // The custom save command is already in this queue and must not await itself.
207232 e . waitUntil ( this . pendingOperation ) ;
208233 }
209234 } ) ;
210235
211- webviewPanel . onDidDispose ( ( ) => {
212- changeDocumentSubscription . dispose ( ) ;
213- saveDocumentSubscription . dispose ( ) ;
214- CsvEditorProvider . editors = CsvEditorProvider . editors . filter ( ed => ed !== this ) ;
215- this . currentWebviewPanel = undefined ;
216- } ) ;
217236 }
218237
219238 private enqueueOperation ( operation : ( ) => void | Promise < void > ) : Promise < void > {
@@ -252,16 +271,30 @@ class CsvEditorController {
252271 }
253272
254273 private async openWithDefaultEditorAndClose ( webviewPanel : vscode . WebviewPanel , uri : vscode . Uri ) : Promise < void > {
255- try {
256- const opts : any = {
257- viewColumn : webviewPanel . viewColumn ,
258- preserveFocus : ! webviewPanel . active ,
259- preview : webviewPanel . active ? webviewPanel . active : false
260- } ;
261- await vscode . commands . executeCommand ( 'vscode.openWith' , uri , 'default' , opts ) ;
262- } finally {
263- try { webviewPanel . dispose ( ) ; } catch { }
274+ if ( this . isDisposed ) {
275+ return ;
264276 }
277+ if ( ! this . defaultEditorHandoff ) {
278+ this . defaultEditorHandoff = ( async ( ) => {
279+ try {
280+ // Search can already have revealed the text editor. Opening it again
281+ // loses its destination, so leave an existing editor in this group alone.
282+ const existing = vscode . window . visibleTextEditors . find ( editor =>
283+ editor . document . uri . toString ( ) === uri . toString ( ) &&
284+ editor . viewColumn === webviewPanel . viewColumn
285+ ) ;
286+ if ( ! existing ) {
287+ await vscode . commands . executeCommand ( 'vscode.openWith' , uri , 'default' , {
288+ viewColumn : webviewPanel . viewColumn ,
289+ preserveFocus : ! webviewPanel . active
290+ } ) ;
291+ }
292+ } finally {
293+ try { webviewPanel . dispose ( ) ; } catch { }
294+ }
295+ } ) ( ) ;
296+ }
297+ await this . defaultEditorHandoff ;
265298 }
266299
267300 private async confirmLargeFileOpen (
@@ -319,8 +352,12 @@ class CsvEditorController {
319352 public refresh ( ) {
320353 const config = vscode . workspace . getConfiguration ( 'csv' , this . document . uri ) ;
321354 if ( ! config . get < boolean > ( 'enabled' , true ) ) {
322- this . currentWebviewPanel ?. dispose ( ) ;
323- vscode . commands . executeCommand ( 'vscode.openWith' , this . document . uri , 'default' ) ;
355+ const panel = this . currentWebviewPanel ;
356+ if ( panel ) {
357+ void this . openWithDefaultEditorAndClose ( panel , this . document . uri ) . catch ( error => {
358+ console . error ( 'CSV: failed to reopen the text editor' , error ) ;
359+ } ) ;
360+ }
324361 } else {
325362 if ( this . currentWebviewPanel ) {
326363 this . forceReload ( ) ;
@@ -1690,6 +1727,12 @@ class CsvEditorController {
16901727 return ! matches ;
16911728 }
16921729
1730+ private getMediaUri ( ...segments : string [ ] ) : vscode . Uri {
1731+ return this . context . extensionUri . with ( {
1732+ path : path . posix . join ( this . context . extensionUri . path , 'media' , ...segments )
1733+ } ) ;
1734+ }
1735+
16931736 private wrapHtml ( args : {
16941737 webview : vscode . Webview ;
16951738 nonce : string ;
@@ -1707,9 +1750,8 @@ class CsvEditorController {
17071750 } ) : string {
17081751 const { webview, nonce, fontFamily, fontSize, cellPadding, separator, tableHtml, chunksJson, extraColumnColorCss, nextChunkStart, hasRemoteChunks, mouseWheelZoomEnabled, mouseWheelZoomInvert } = args ;
17091752 const isDark = vscode . window . activeColorTheme . kind === vscode . ColorThemeKind . Dark ;
1710- // Build script URI using file path for compatibility (older APIs may lack Uri.joinPath)
17111753 const scriptUri = webview . asWebviewUri (
1712- vscode . Uri . file ( path . join ( this . context . extensionPath , 'media' , ' main.js') )
1754+ this . getMediaUri ( ' main.js')
17131755 ) ;
17141756
17151757 // Safe separator transport (assumes single character; see assumptions)
@@ -2539,17 +2581,12 @@ export class CsvEditorProvider implements vscode.CustomTextEditorProvider {
25392581 ) : Promise < void > {
25402582 console . log ( `CSV(reg): creating controller for ${ document . uri . toString ( ) } ` ) ;
25412583 const controller = new CsvEditorController ( this . context ) ;
2542- // Track active controller
2543- webviewPanel . onDidChangeViewState ( e => {
2544- if ( e . webviewPanel . active ) {
2545- CsvEditorProvider . currentActive = controller ;
2546- }
2547- } ) ;
25482584 await controller . resolveCustomTextEditor ( document , webviewPanel , _token ) ;
25492585 }
25502586
25512587 public static getActiveProvider ( ) : CsvEditorController | undefined {
2552- return CsvEditorProvider . currentActive || CsvEditorProvider . editors . find ( ed => ed . isActive ( ) ) ;
2588+ const current = CsvEditorProvider . currentActive ;
2589+ return current ?. isActive ( ) ? current : CsvEditorProvider . editors . find ( ed => ed . isActive ( ) ) ;
25532590 }
25542591
25552592 public static getHiddenRowsForUri ( context : vscode . ExtensionContext , uri : vscode . Uri ) : number {
0 commit comments