From fb08c33ada3395573a40345fcdaa4fbc432d62d2 Mon Sep 17 00:00:00 2001 From: WolfGangS Date: Sat, 18 Apr 2026 03:27:11 +0100 Subject: [PATCH 1/9] First pass of new sync setup --- package.json | 39 +++++++++++ src/extension.ts | 68 +++++++++++------- src/interfaces/configinterface.ts | 1 + src/scriptsync.ts | 27 +++++--- src/synchservice.ts | 111 +++++++++++++++++++----------- src/vscode/SyncedFileDecorator.ts | 35 ++++++++++ 6 files changed, 208 insertions(+), 73 deletions(-) create mode 100644 src/vscode/SyncedFileDecorator.ts diff --git a/package.json b/package.json index 88a3cb1..96e5904 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,40 @@ "command": "second-life-scripting.forceLanguageUpdate", "title": "Force Language Update", "category": "Second Life" + }, + { + "command": "second-life-scripting.stopFileSync", + "shortTitle": "Stop File Sync", + "title": "Stop file sync with SL Viewer", + "category": "Second Life" + } + ], + "menus": { + "explorer/context": [ + { + "command": "second-life-scripting.stopFileSync", + "group": "navigation@99", + "when": "resourceScheme == file && slVscodeEdit:syncsActive" + } + ], + "editor/title/context": [ + { + "command": "second-life-scripting.stopFileSync", + "group": "1_close@99", + "when": "resourceScheme == file && slVscodeEdit:syncsActive" + } + ] + }, + "colors": [ + { + "id": "secondlife.syncedfile", + "description": "Color for files that are being synced with the viewer", + "defaults": { + "dark": "tab.activeBorderTop", + "light": "tab.activeBorderTop", + "highContrast": "tab.activeBorderTop", + "highContrastLight": "tab.activeBorderTop" + } } ], "configuration": [ @@ -105,6 +139,11 @@ "type": "boolean", "default": false, "description": "Whether the current sl user info should be included in the meta info" + }, + "slVscodeEdit.sync.keepViewerFileOpen": { + "type": "boolean", + "default": true, + "description": "Should the viewers tempfile be kept open while editing" } } }, diff --git a/src/extension.ts b/src/extension.ts index b469d8a..925d701 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -16,6 +16,7 @@ import { showErrorMessage } from "./utils"; import { ConfigKey } from "./interfaces/configinterface"; +import path from "path"; // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -34,7 +35,35 @@ export function activate(context: vscode.ExtensionContext): void { showErrorMessage("Second Life Scripting Extension: No workspace is opened.\nPlease open a folder in VSCode to enable full functionality."); } + setupCommands(context); + configService.on(ConfigKey.Enabled, (configService) => { + if(configService.isEnabled()) { + synchService.activate(); + logInfo("Second Life Scripting Extension activated"); + } else { + synchService.deactivate(); + logInfo("Second Life Scripting Extension deactivated"); + } + }); + + if(configService.isEnabled()) { + synchService.activate(); + logInfo("Second Life Scripting Extension activated"); + } + + context.subscriptions.push(configService); + context.subscriptions.push(languageService); + context.subscriptions.push(synchService); +} + +// This method is called when your extension is deactivated +export function deactivate(): void { + const synchService = SynchService.getInstance(); + synchService.deactivate(); +} + +function setupCommands(context: vscode.ExtensionContext) { // Register commands context.subscriptions.push( vscode.commands.registerCommand( @@ -89,28 +118,19 @@ export function activate(context: vscode.ExtensionContext): void { ) ); - configService.on(ConfigKey.Enabled, (configService) => { - if(configService.isEnabled()) { - synchService.activate(); - logInfo("Second Life Scripting Extension activated"); - } else { - synchService.deactivate(); - logInfo("Second Life Scripting Extension deactivated"); - } - }); - - if(configService.isEnabled()) { - synchService.activate(); - logInfo("Second Life Scripting Extension activated"); - } - - context.subscriptions.push(configService); - context.subscriptions.push(languageService); - context.subscriptions.push(synchService); -} - -// This method is called when your extension is deactivated -export function deactivate(): void { - const synchService = SynchService.getInstance(); - synchService.deactivate(); + context.subscriptions.push( + vscode.commands.registerCommand( + "second-life-scripting.stopFileSync", + (uri?: vscode.Uri) => { + console.error("CLOSE SYNC COMMAND", uri); + if(!uri) { + uri = vscode.window.activeTextEditor?.document.uri; + } + if(!uri) { + return; + } + SynchService.getInstance().removeSync(path.normalize(uri.fsPath), false); + } + ) + ); } diff --git a/src/interfaces/configinterface.ts b/src/interfaces/configinterface.ts index c4c032a..b3ff02d 100644 --- a/src/interfaces/configinterface.ts +++ b/src/interfaces/configinterface.ts @@ -33,6 +33,7 @@ export enum ConfigKey { LastSyntaxID = 'syntax.lastID', AskIfViewerScriptMismatchesMaster = 'sync.askIfViewerScriptMismatchesMaster', CompareHashBeforeSync = 'sync.compareHashBeforeSync', + KeepViewerFileOpen = 'sync.keepViewerFileOpen', FileMetaInfoInOutput ='sync.includeFileMetaInOutput', FileMetaInfoIncludeCreator ='sync.includeCreatorInFileMeta', diff --git a/src/scriptsync.ts b/src/scriptsync.ts index c03b79f..4bc3459 100644 --- a/src/scriptsync.ts +++ b/src/scriptsync.ts @@ -51,18 +51,19 @@ export class ScriptSync implements vscode.Disposable { private diagnosticSources: Set = new Set(); private lineMappings?: LineMapping[]; private config: ConfigService; - private host: HostInterface; + // private host: HostInterface; private includedFiles : IncludeInfo[] = []; + private syncService: SynchService; //==================================================================== public constructor( masterDocument: vscode.TextDocument, language: ScriptLanguage, config: ConfigService, - scriptId?: string, - viewerDocument?: vscode.TextDocument, - host?: HostInterface, + scriptId: string, + viewerDocument: vscode.TextDocument, + syncService: SynchService, ) { this.config = config; @@ -71,12 +72,12 @@ export class ScriptSync implements vscode.Disposable { this.macros = new MacroProcessor(); this.initializeSystemMacros(language); - this.host = host ?? new VSCodeHost(); + this.syncService = syncService; // Initialize preprocessor with macro processor const enabled = config.getConfig(ConfigKey.PreprocessorEnable) ?? true; if (enabled) { - this.preprocessor = new LexingPreprocessor(this.host, config, this.macros); + this.preprocessor = new LexingPreprocessor(this.syncService.getHost(), config, this.macros); } this.masterDocument = masterDocument; @@ -146,6 +147,10 @@ export class ScriptSync implements vscode.Disposable { this.fileMappings = this.fileMappings.filter((m) => m !== mapping); if (close) { closeTextDocument(mapping.viewerDocument); + mapping.watcher?.dispose(); + } + if(!this.hasFilesToTrack()) { + this.syncService.clearEmptySyncs(); } } return this.fileMappings.length; @@ -157,10 +162,7 @@ export class ScriptSync implements vscode.Disposable { (m) => path.normalize(m.viewerDocument.fileName) === viewerFile, ); if (mapping) { - this.fileMappings = this.fileMappings.filter((m) => m !== mapping); - if (close) { - closeTextDocument(mapping.viewerDocument); - } + this.unsubscribeById(mapping.id, close); } return this.fileMappings.length; } @@ -178,6 +180,10 @@ export class ScriptSync implements vscode.Disposable { ); } + public hasFilesToTrack() : boolean { + return this.fileMappings.length > 0; + } + public getMasterDocument(): vscode.TextDocument { return this.masterDocument; } @@ -580,6 +586,7 @@ export class ScriptSync implements vscode.Disposable { try { this.diagnosticCollection.dispose(); + this.fileMappings.forEach(map => map.watcher?.dispose()); } catch (error) { // Log but don't throw during disposal console.warn("Error during ScriptSync disposal:", error); diff --git a/src/synchservice.ts b/src/synchservice.ts index a562a2c..0a3b3a2 100644 --- a/src/synchservice.ts +++ b/src/synchservice.ts @@ -28,12 +28,14 @@ import { closeEditor, logInfo, VSCodeHost, + closeTextDocument, } from "./utils"; import { maybe } from "./shared/sharedutils"; // TODO: migrate needed utilities from sharedutils if required import { ScriptLanguage, LanguageService } from "./shared/languageservice"; import { ScriptSync } from "./scriptsync"; import { getLanguageConfig } from "./shared/lexer"; import { HostInterface } from "./interfaces/hostinterface"; +import { SyncedFileDecorator } from "./vscode/SyncedFileDecorator"; type ParsedTempFile = { scriptName: string; scriptId: string; extension: string, language: ScriptLanguage }; @@ -58,11 +60,14 @@ export class SynchService implements vscode.Disposable { public agentId?: string; public agentName?: string; + private syncedFileDecorator : SyncedFileDecorator; + private disposables: vscode.Disposable[] = []; private constructor(context: vscode.ExtensionContext) { this.context = context; this.host = new VSCodeHost(); + this.syncedFileDecorator = new SyncedFileDecorator(this); } public static getInstance(context?: vscode.ExtensionContext): SynchService { @@ -93,20 +98,29 @@ export class SynchService implements vscode.Disposable { this.disposables = []; } + public getHost() : HostInterface + { + return this.host; + } + public initialize(): void { const onDidOpenListener = vscode.workspace.onDidOpenTextDocument( async (document) => this.onOpenTextDocument(document), ); - const onDidCloseListener = vscode.workspace.onDidCloseTextDocument( - (document: vscode.TextDocument) => this.onCloseTextDocument(document), - ); + // const onDidCloseListener = vscode.workspace.onDidCloseTextDocument( + // (document: vscode.TextDocument) => this.onCloseTextDocument(document), + // ); const onDidDeleteListener = vscode.workspace.onDidDeleteFiles( (event: vscode.FileDeleteEvent) => this.onDeleteFiles(event), ); + const onDidCloseWorkspace = vscode.workspace.onDidChangeWorkspaceFolders((e) => { + e.removed.forEach(folder => this.onCloseWorkspace(folder)); + }); + const onDidSaveListener = vscode.workspace.onDidSaveTextDocument( (document: vscode.TextDocument) => this.onSaveTextDocument(document), ); @@ -131,11 +145,13 @@ export class SynchService implements vscode.Disposable { // showStatusMessage("Initializing syntax...", syntaxInit); this.disposables.push(onDidOpenListener); - this.disposables.push(onDidCloseListener); + // this.disposables.push(onDidCloseListener); + this.disposables.push(onDidCloseWorkspace); this.disposables.push(onDidDeleteListener); this.disposables.push(onDidSaveListener); this.disposables.push(onDidChangeWindowState); this.disposables.push(onDidChangeActiveTextEditor); + this.disposables.push(vscode.window.registerFileDecorationProvider(this.syncedFileDecorator)); } private async initializeSyntax(): Promise { @@ -165,7 +181,7 @@ export class SynchService implements vscode.Disposable { private async setupSync( viewerDocument: vscode.TextDocument, ): Promise { - const viewerFilePath = path.normalize(viewerDocument.fileName); + const viewerFilePath = path.normalize(viewerDocument.uri.fsPath); const openedBase = path.basename(viewerFilePath); if (!hasWorkspace()) { @@ -196,7 +212,7 @@ export class SynchService implements vscode.Disposable { showInfoMessage(`Opening master script: ${path.basename(masterPath)}`); let masterEditor = await SynchService.openMasterScript(masterUri); let masterDoc = masterEditor.document - SynchService.checkAndUpdateMasterDocumentInBackground(masterEditor, viewerDocument) + SynchService.checkAndUpdateMasterDocumentInBackground(masterEditor, viewerDocument); // Connection goes on in the background let viewerConnecting: Promise = this.setupConnection(); @@ -217,62 +233,75 @@ export class SynchService implements vscode.Disposable { } }); - let sync = this.findSyncByTempFilePath(viewerFilePath) ?? - this.findSyncByMasterFilePath(masterPath); - if (sync) { + const masterSync = this.findSyncByMasterFilePath(masterPath); + const syncs : ScriptSync[] = []; + + if(masterSync) { + syncs.push(masterSync); + } else { + syncs.push(...this.findSyncsByTempFilePath(viewerFilePath)); + } + + if(!this.host.config.getConfig(ConfigKey.KeepViewerFileOpen, true)) { + closeTextDocument(viewerDocument); + } + + if(syncs.length) { // Already syncing the master, add another id and viewer file - sync.subscribe(parsed.scriptId, viewerDocument); + syncs.forEach(sync => sync.subscribe(parsed.scriptId, viewerDocument)); } else { const config = ConfigService.getInstance(); - sync = new ScriptSync( + const sync = new ScriptSync( masterDoc, parsed.extension as ScriptLanguage, config, parsed.scriptId, viewerDocument, - this.host, + this, ); await sync.initialize(); this.activeSyncs.set(masterPath, sync); + syncs.push(sync); } + syncs.forEach(sync => this.syncedFileDecorator.refresh(sync.getMasterDocument().uri)); + if (this.websocket && this.websocket.isConnected()) { - this.sendSyncSubscription(sync); + syncs.forEach(sync => this.sendSyncSubscription(sync)); } else { viewerConnecting.then((connected) => { if (connected) { - this.sendSyncSubscription(sync); + syncs.forEach(sync=>this.sendSyncSubscription(sync)); } }); } + this.clearEmptySyncs(); + return true; } public removeSync(filePath: string, close: boolean): void { // seeing if we closed a temp file or a master file - let sync = - this.findSyncByTempFilePath(filePath) ?? - this.findSyncByMasterFilePath(filePath); + let sync = this.findSyncByMasterFilePath(filePath); if (!sync) { // No sync found for this file, we are not tracking it return; } - if (sync.getMasterFilePath() !== filePath) { - // We only destroy the sync if the master file is closed - // This is so we can continue to handle preprocessor directives while editing. - this.activeSyncs.delete(sync.getMasterFilePath()); - sync.dispose(); - } else { - // This is not the master file, just remove the tracking links. - const parsed = SynchService.parseTempFile(filePath); - if (parsed) { - // Remove the tracking subscription, if there are no more tracked files we will dispose the sync - sync.unsubscribeById(parsed.scriptId); - if (close) { - closeEditor(filePath); - } + this.activeSyncs.delete(sync.getMasterFilePath()); + this.syncedFileDecorator.refresh(sync.getMasterDocument().uri); + sync.dispose(); + + this.clearEmptySyncs(); + } + + public clearEmptySyncs() { + for(const [key,sync] of this.activeSyncs) { + if(!sync.hasFilesToTrack()) { + this.activeSyncs.delete(key); + this.syncedFileDecorator.refresh(sync.getMasterDocument().uri); + sync.dispose(); } } @@ -286,6 +315,11 @@ export class SynchService implements vscode.Disposable { this.websocket = undefined; } } + vscode.commands.executeCommand( + "setContext", + "slVscodeEdit:syncsActive", + this.activeSyncs.size > 0 + ); } //==================================================================== @@ -597,9 +631,9 @@ export class SynchService implements vscode.Disposable { ); } - public findSyncByTempFilePath(filePath: string): ScriptSync | undefined { + public findSyncsByTempFilePath(filePath: string): ScriptSync[] { filePath = path.normalize(filePath); - return [...this.activeSyncs.values()].find((sync) => + return [...this.activeSyncs.values()].filter((sync) => sync.isTrackingFile(filePath), ); } @@ -723,9 +757,8 @@ export class SynchService implements vscode.Disposable { this.initializeSyntax(); } - private onCloseTextDocument(document: vscode.TextDocument): void { - const filePath = path.normalize(document.fileName); - this.removeSync(filePath, false); + private onCloseWorkspace(workspace: vscode.WorkspaceFolder) : void { + console.error("WORKSPACE CLOSE",workspace.name,workspace.uri.fsPath); } private onDeleteFiles(event: vscode.FileDeleteEvent): void { @@ -768,13 +801,13 @@ export class SynchService implements vscode.Disposable { // if the viewer launched it we will soon get a foucus event (onChangeWindowState) // Find the sync for this file, if any and then record the time. const filePath = path.normalize(editor.document.fileName); - const sync = this.findSyncByTempFilePath(filePath); - if (sync) { + const syncs = this.findSyncsByTempFilePath(filePath); + if (syncs.length) { // We have a sync for this file, record the time // We'll use this to see if a focus event happens very soon after // this event, if so we can assume the viewer launched us this.lastActiveChange = Date.now(); - this.activeSync = sync; + this.activeSync = syncs.pop(); } } //#endregion diff --git a/src/vscode/SyncedFileDecorator.ts b/src/vscode/SyncedFileDecorator.ts new file mode 100644 index 0000000..21ce5a0 --- /dev/null +++ b/src/vscode/SyncedFileDecorator.ts @@ -0,0 +1,35 @@ +import { + FileDecorationProvider, + Uri, + FileDecoration, + EventEmitter, + ProviderResult, + CancellationToken, + ThemeColor +} from "vscode"; + +import { SynchService } from "../synchservice"; + +export class SyncedFileDecorator implements FileDecorationProvider { + private syncService: SynchService; + private _onDidChangeFileDecorations = new EventEmitter(); + readonly onDidChangeFileDecorations = this._onDidChangeFileDecorations.event; + + constructor(syncService:SynchService) { + this.syncService = syncService; + } + + provideFileDecoration(uri: Uri, token: CancellationToken): ProviderResult { + if(this.syncService.findSyncByMasterFilePath(uri.fsPath)) { + return { + badge: '🔗', + tooltip: 'Synchronized with secondlife viewer', + color: new ThemeColor('tab.activeBorderTop'), + }; + } + } + + public refresh(uri?: Uri | Uri[]) : void { + this._onDidChangeFileDecorations.fire(uri); + } +} From e0d3e1b10b2277fd6fd6cd88e166c51e5e390594 Mon Sep 17 00:00:00 2001 From: WolfGangS Date: Sat, 18 Apr 2026 03:40:10 +0100 Subject: [PATCH 2/9] Dont close file if no master found --- src/synchservice.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/synchservice.ts b/src/synchservice.ts index 0a3b3a2..8b7e227 100644 --- a/src/synchservice.ts +++ b/src/synchservice.ts @@ -199,7 +199,9 @@ export class SynchService implements vscode.Disposable { // Look for a file in the workspace with the same name as the master script let masterUri = await SynchService.findMasterFile(parsed, viewerDocument); + let masterFound = true; if (!masterUri) { + masterFound = false; // There was no master file found, we are our own master showInfoMessage( `No master script found for: ${parsed.scriptName}.${parsed.extension}`, @@ -242,7 +244,7 @@ export class SynchService implements vscode.Disposable { syncs.push(...this.findSyncsByTempFilePath(viewerFilePath)); } - if(!this.host.config.getConfig(ConfigKey.KeepViewerFileOpen, true)) { + if(!this.host.config.getConfig(ConfigKey.KeepViewerFileOpen, true) && masterFound) { closeTextDocument(viewerDocument); } From 243e01000251dcd2d21599e346747118626f7dab Mon Sep 17 00:00:00 2001 From: WolfGangS Date: Sat, 18 Apr 2026 03:46:25 +0100 Subject: [PATCH 3/9] linting --- src/extension.ts | 4 ++-- src/scriptsync.ts | 2 +- src/synchservice.ts | 7 +++---- src/vscode/SyncedFileDecorator.ts | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 925d701..755f024 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -63,7 +63,7 @@ export function deactivate(): void { synchService.deactivate(); } -function setupCommands(context: vscode.ExtensionContext) { +function setupCommands(context: vscode.ExtensionContext) : void { // Register commands context.subscriptions.push( vscode.commands.registerCommand( @@ -129,7 +129,7 @@ function setupCommands(context: vscode.ExtensionContext) { if(!uri) { return; } - SynchService.getInstance().removeSync(path.normalize(uri.fsPath), false); + SynchService.getInstance().removeSync(path.normalize(uri.fsPath)); } ) ); diff --git a/src/scriptsync.ts b/src/scriptsync.ts index 4bc3459..1ee37cd 100644 --- a/src/scriptsync.ts +++ b/src/scriptsync.ts @@ -25,7 +25,7 @@ import { } from "./utils"; import { ScriptLanguage } from "./shared/languageservice"; import { CompilationResult, RuntimeDebug, RuntimeError } from "./viewereditwsclient"; -import { HostInterface, normalizePath } from "./interfaces/hostinterface"; +import { normalizePath } from "./interfaces/hostinterface"; import { SynchService } from "./synchservice"; import { IncludeInfo } from "./shared/parser"; import { sha256 } from "js-sha256"; diff --git a/src/synchservice.ts b/src/synchservice.ts index 8b7e227..0026fcb 100644 --- a/src/synchservice.ts +++ b/src/synchservice.ts @@ -25,7 +25,6 @@ import { showInfoMessage, showStatusMessage, showWarningMessage, - closeEditor, logInfo, VSCodeHost, closeTextDocument, @@ -283,7 +282,7 @@ export class SynchService implements vscode.Disposable { return true; } - public removeSync(filePath: string, close: boolean): void { + public removeSync(filePath: string): void { // seeing if we closed a temp file or a master file let sync = this.findSyncByMasterFilePath(filePath); if (!sync) { @@ -298,7 +297,7 @@ export class SynchService implements vscode.Disposable { this.clearEmptySyncs(); } - public clearEmptySyncs() { + public clearEmptySyncs() : void { for(const [key,sync] of this.activeSyncs) { if(!sync.hasFilesToTrack()) { this.activeSyncs.delete(key); @@ -767,7 +766,7 @@ export class SynchService implements vscode.Disposable { const uris = event.files; uris.forEach((uri) => { const filePath = path.normalize(uri.fsPath); - this.removeSync(filePath, false); + this.removeSync(filePath); }); } diff --git a/src/vscode/SyncedFileDecorator.ts b/src/vscode/SyncedFileDecorator.ts index 21ce5a0..816b9e5 100644 --- a/src/vscode/SyncedFileDecorator.ts +++ b/src/vscode/SyncedFileDecorator.ts @@ -19,7 +19,7 @@ export class SyncedFileDecorator implements FileDecorationProvider { this.syncService = syncService; } - provideFileDecoration(uri: Uri, token: CancellationToken): ProviderResult { + provideFileDecoration(uri: Uri, _token: CancellationToken): ProviderResult { if(this.syncService.findSyncByMasterFilePath(uri.fsPath)) { return { badge: '🔗', From fc07cfed264bc43a1f5f045728c04806801bc71d Mon Sep 17 00:00:00 2001 From: WolfGang Date: Thu, 30 Apr 2026 17:49:47 +0100 Subject: [PATCH 4/9] Remove Debug Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/extension.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 755f024..4ad5f35 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -122,7 +122,6 @@ function setupCommands(context: vscode.ExtensionContext) : void { vscode.commands.registerCommand( "second-life-scripting.stopFileSync", (uri?: vscode.Uri) => { - console.error("CLOSE SYNC COMMAND", uri); if(!uri) { uri = vscode.window.activeTextEditor?.document.uri; } From e50c8b57174ae7d7c42ddeb49aa2eb3403d202e2 Mon Sep 17 00:00:00 2001 From: WolfGang Date: Thu, 30 Apr 2026 17:50:24 +0100 Subject: [PATCH 5/9] Handle close textDocument errors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/synchservice.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/synchservice.ts b/src/synchservice.ts index 7527c20..78a7fe8 100644 --- a/src/synchservice.ts +++ b/src/synchservice.ts @@ -250,7 +250,11 @@ export class SynchService implements vscode.Disposable { } if(!this.host.config.getConfig(ConfigKey.KeepViewerFileOpen, true) && masterFound) { - closeTextDocument(viewerDocument); + void closeTextDocument(viewerDocument).catch((error) => { + logInfo( + `Failed to auto-close viewer document ${viewerDocument.uri.fsPath}: ${error instanceof Error ? error.message : String(error)}`, + ); + }); } if(syncs.length) { From dfc90199a1b2ae8cf4960eff429ef086b1085812 Mon Sep 17 00:00:00 2001 From: WolfGang Date: Thu, 30 Apr 2026 17:50:47 +0100 Subject: [PATCH 6/9] Normalize file path before setting in activeSyncs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/synchservice.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/synchservice.ts b/src/synchservice.ts index 78a7fe8..657d2d2 100644 --- a/src/synchservice.ts +++ b/src/synchservice.ts @@ -271,7 +271,8 @@ export class SynchService implements vscode.Disposable { this, ); await sync.initialize(); - this.activeSyncs.set(masterPath, sync); + const normalizedMasterPath = path.normalize(masterPath); + this.activeSyncs.set(normalizedMasterPath, sync); syncs.push(sync); } From e0386b48830bfbfb2d9955bf207391512259f85c Mon Sep 17 00:00:00 2001 From: WolfGang Date: Thu, 30 Apr 2026 17:52:09 +0100 Subject: [PATCH 7/9] Update src/vscode/SyncedFileDecorator.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vscode/SyncedFileDecorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode/SyncedFileDecorator.ts b/src/vscode/SyncedFileDecorator.ts index 816b9e5..e5f818e 100644 --- a/src/vscode/SyncedFileDecorator.ts +++ b/src/vscode/SyncedFileDecorator.ts @@ -24,7 +24,7 @@ export class SyncedFileDecorator implements FileDecorationProvider { return { badge: '🔗', tooltip: 'Synchronized with secondlife viewer', - color: new ThemeColor('tab.activeBorderTop'), + color: new ThemeColor('secondlife.syncedfile'), }; } } From e868284c4eb2caa32cbe1994cb09fbc82a04142c Mon Sep 17 00:00:00 2001 From: WolfGang Date: Thu, 30 Apr 2026 17:53:22 +0100 Subject: [PATCH 8/9] Update src/vscode/SyncedFileDecorator.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vscode/SyncedFileDecorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode/SyncedFileDecorator.ts b/src/vscode/SyncedFileDecorator.ts index e5f818e..3f175fb 100644 --- a/src/vscode/SyncedFileDecorator.ts +++ b/src/vscode/SyncedFileDecorator.ts @@ -8,7 +8,7 @@ import { ThemeColor } from "vscode"; -import { SynchService } from "../synchservice"; +import type { SynchService } from "../synchservice"; export class SyncedFileDecorator implements FileDecorationProvider { private syncService: SynchService; From f10443b4bbbd5c5cdc4ce7c79dd25a268357b1ca Mon Sep 17 00:00:00 2001 From: WolfGang Date: Thu, 30 Apr 2026 17:56:18 +0100 Subject: [PATCH 9/9] Update src/synchservice.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/synchservice.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/synchservice.ts b/src/synchservice.ts index 657d2d2..3a12474 100644 --- a/src/synchservice.ts +++ b/src/synchservice.ts @@ -857,7 +857,17 @@ export class SynchService implements vscode.Disposable { } private onCloseWorkspace(workspace: vscode.WorkspaceFolder) : void { - console.error("WORKSPACE CLOSE",workspace.name,workspace.uri.fsPath); + const workspacePath = path.normalize(workspace.uri.fsPath); + const workspacePrefix = workspacePath.endsWith(path.sep) + ? workspacePath + : workspacePath + path.sep; + + for (const document of vscode.workspace.textDocuments) { + const filePath = path.normalize(document.fileName); + if (filePath === workspacePath || filePath.startsWith(workspacePrefix)) { + this.removeSync(filePath); + } + } } private onDeleteFiles(event: vscode.FileDeleteEvent): void {