From 41f6ea5854fe0270d2a4398272ea62e39063109d Mon Sep 17 00:00:00 2001
From: itdominator <1itdominator@gmail.com>
Date: Fri, 20 Jun 2025 00:50:43 -0500
Subject: [PATCH] Wiring file watch events WIP

---
 newton/fs.js                                  | 50 ++++++++++++++-----
 newton/preload.js                             |  2 +
 .../common/services/editor/editors.service.ts |  2 +-
 .../common/services/editor/files.service.ts   |  9 ++++
 src/app/editor/editors.component.ts           | 40 ++++++++++++---
 .../newton-editor/newton-editor.component.ts  |  9 ++++
 src/app/editor/tabs/tabs.component.css        | 12 +++++
 src/app/editor/tabs/tabs.component.ts         | 18 +++++--
 src/polyfills.ts                              |  2 +
 9 files changed, 119 insertions(+), 25 deletions(-)

diff --git a/newton/fs.js b/newton/fs.js
index ba635a3..e704cb0 100644
--- a/newton/fs.js
+++ b/newton/fs.js
@@ -5,13 +5,14 @@ const os             = require('os');
 const chokidar       = require('chokidar');
 
 
-const HOME_DIR              = os.homedir();
-const BASE_PATH             = '../build/app';
-const CONFIG_PATH           = path.join(HOME_DIR, "/.config/newton/");
-const SETTINGS_CONFIG_PATH  = path.join(CONFIG_PATH, "/settings.json");
-const LSP_CONFIG_PATH       = path.join(BASE_PATH, "/resources/lsp-servers-config.json");
-let window                  = null;
-let watcher                 = null;
+const HOME_DIR                = os.homedir();
+const BASE_PATH               = '../build/app';
+const CONFIG_PATH             = path.join(HOME_DIR, "/.config/newton/");
+const SETTINGS_CONFIG_PATH    = path.join(CONFIG_PATH, "/settings.json");
+const LSP_CONFIG_PATH         = path.join(BASE_PATH, "/resources/lsp-servers-config.json");
+let window                    = null;
+let watcher                   = null;
+let skipWatchChangeUpdateOnce = false;
 
 
 
@@ -63,21 +64,39 @@ const saveSettingsConfigData = (data) => {
 }
 
 const saveFile = (fpath, content)  => {
+    skipWatchChangeUpdateOnce = true;
+
     fs.writeFile(fpath, content, (err) => {
-        if (!err) return
-        console.error("An error ocurred writing to the file " + err.message);
+        if (err) {
+            console.error("An error ocurred writing to the file " + err.message);
+            return;
+        }
+
+        let parentDir = path.dirname(fpath);
+        let watchers  = watcher.getWatched();
+        let targetDir = watchers[parentDir];
+        if (
+            targetDir && !targetDir.includes( path.basename(fpath) )
+        ) {
+            skipWatchChangeUpdateOnce = false;
+            watcher.add(fpath);
+            window.webContents.send('update-file-path', fpath);
+        }
+
+        try {
+            window.webContents.send('file-saved', fpath);
+        } catch(e) {}
     });
 }
 
 const saveFileAs = (content) => {
     dialog.showSaveDialog().then((response) => {
         if (response.canceled) {
-            console.log("You didn't save the file");
+            console.debug("You didn't save the file");
             return;
         }
 
         saveFile(response.filePath, content);
-        watcher.add(response.filePath);
     });
 }
 
@@ -111,7 +130,7 @@ const openFiles = (startPath) => {
         }
     ).then((response) => {
         if (response.canceled) {
-            console.log("Canceled file(s) open request...");
+            console.debug("Canceled file(s) open request...");
             return;
         }
 
@@ -124,6 +143,11 @@ const loadFilesWatcher = () => {
     watcher = chokidar.watch([], {});
 
     watcher.on('change', (fpath) => {
+        if (skipWatchChangeUpdateOnce) {
+            skipWatchChangeUpdateOnce = false;
+            return;
+        }
+
         console.debug("File (changed) : ", fpath);
         window.webContents.send('file-changed', fpath);
     }).on('unlink', (fpath) => {
@@ -133,7 +157,7 @@ const loadFilesWatcher = () => {
 }
 
 const unwatchFile = async (fpath) => {
-    console.log("File (unwatch) : ", fpath);
+    console.debug("File (unwatch) : ", fpath);
     await watcher.unwatch(fpath);
 }
 
diff --git a/newton/preload.js b/newton/preload.js
index 5646dfe..5af2649 100644
--- a/newton/preload.js
+++ b/newton/preload.js
@@ -22,6 +22,8 @@ contextBridge.exposeInMainWorld('fs', {
     closeFile: (path) => ipcRenderer.invoke("closeFile", path),
     getPathForFile: (file) => webUtils.getPathForFile(file),
     onLoadFiles: (callback) => ipcRenderer.on('load-files', (_event, paths) => callback(paths)),
+    onUpdateFilePath: (callback) => ipcRenderer.on('update-file-path', (_event, paths) => callback(paths)),
+    onSavedFile: (callback) => ipcRenderer.on('file-saved', (_event, path) => callback(path)),
     onChangedFile: (callback) => ipcRenderer.on('file-changed', (_event, path) => callback(path)),
     onDeletedFile: (callback) => ipcRenderer.on('file-deleted', (_event, path) => callback(path)),
 });
\ No newline at end of file
diff --git a/src/app/common/services/editor/editors.service.ts b/src/app/common/services/editor/editors.service.ts
index 22f398e..e84728b 100644
--- a/src/app/common/services/editor/editors.service.ts
+++ b/src/app/common/services/editor/editors.service.ts
@@ -1,5 +1,5 @@
 import { ComponentRef, Injectable } from '@angular/core';
-import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
+import { ReplaySubject, Observable } from 'rxjs';
 
 import { NewtonEditorComponent } from "../../../editor/newton-editor/newton-editor.component";
 
diff --git a/src/app/common/services/editor/files.service.ts b/src/app/common/services/editor/files.service.ts
index 6af20f9..f16d6a2 100644
--- a/src/app/common/services/editor/files.service.ts
+++ b/src/app/common/services/editor/files.service.ts
@@ -1,4 +1,5 @@
 import { Injectable } from '@angular/core';
+import { ReplaySubject, Observable } from 'rxjs';
 
 import { EditSession } from 'ace-builds';
 import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist';
@@ -14,6 +15,8 @@ import { ServiceMessage } from '../../types/service-message.type';
     providedIn: 'root'
 })
 export class FilesService {
+    private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
+
     files: Map<string, NewtonFile>;
 
 
@@ -38,7 +41,13 @@ export class FilesService {
         this.files.set(file.path, file);
     }
 
+    sendMessage(data: ServiceMessage): void {
+        this.messageSubject.next(data);
+    }
 
+    getMessage$(): Observable<ServiceMessage> {
+        return this.messageSubject.asObservable();
+    }
 
     async loadFilesList(files: Array<NewtonFile>): Promise<NewtonFile | undefined | null> {
 	    for (let i = 0; i < files.length; i++) {
diff --git a/src/app/editor/editors.component.ts b/src/app/editor/editors.component.ts
index 55cac4f..66f7cb7 100644
--- a/src/app/editor/editors.component.ts
+++ b/src/app/editor/editors.component.ts
@@ -4,6 +4,7 @@ import { Subject, takeUntil } from 'rxjs';
 import { NewtonEditorComponent } from "./newton-editor/newton-editor.component";
 import { FilesModalComponent } from "./modals/files-modal.component";
 import { EditorsService } from '../common/services/editor/editors.service';
+import { TabsService } from '../common/services/editor/tabs/tabs.service';
 import { FilesService } from '../common/services/editor/files.service';
 
 import { DndDirective } from '../common/directives/dnd.directive';
@@ -34,6 +35,7 @@ export class EditorsComponent {
 
     constructor(
         private editorsService: EditorsService,
+        private tabsService: TabsService,
         private filesService: FilesService
     ) {
     }
@@ -57,17 +59,17 @@ export class EditorsComponent {
         this.editorsService.getMessage$().pipe(
             takeUntil(this.unsubscribe)
         ).subscribe((message: ServiceMessage) => {
-            if (message.action == "select-left-editor") {
+            if (message.action === "select-left-editor") {
                 let editorComponent  = this.editorsService.get(message.editorUUID);
                 if (!editorComponent.leftSiblingUUID) return;
                 let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID);
                 siblingComponent.editor.focus()
-            } else if (message.action == "select-right-editor") {
+            } else if (message.action === "select-right-editor") {
                 let editorComponent  = this.editorsService.get(message.editorUUID);
                 if (!editorComponent.rightSiblingUUID) return;
                 let siblingComponent = this.editorsService.get(editorComponent.rightSiblingUUID);
                 siblingComponent.editor.focus()
-            } else if (message.action == "move-session-left") {
+            } else if (message.action === "move-session-left") {
                 let editorComponent  = this.editorsService.get(message.editorUUID);
                 if (!editorComponent.leftSiblingUUID) return;
                 let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID);
@@ -78,7 +80,7 @@ export class EditorsComponent {
                 siblingComponent.editor.setSession(session);
                 editorComponent.newBuffer();
                 siblingComponent.editor.focus()
-            } else if (message.action == "move-session-right") {
+            } else if (message.action === "move-session-right") {
                 let editorComponent  = this.editorsService.get(message.editorUUID);
                 if (!editorComponent.rightSiblingUUID) return;
                 let siblingComponent = this.editorsService.get(editorComponent.rightSiblingUUID);
@@ -89,18 +91,18 @@ export class EditorsComponent {
                 siblingComponent.editor.setSession(session);
                 editorComponent.newBuffer();
                 siblingComponent.editor.focus()
-            } else if (message.action == "set-active-editor") {
+            } else if (message.action === "set-active-editor") {
                 this.editorsService.get(this.activeEditor).removeActiveStyling();
                 this.activeEditor = message.editorUUID;
                 this.editorsService.get(this.activeEditor).addActiveStyling();
-            } else if (message.action == "set-tab-to-editor") {
+            } else if (message.action === "set-tab-to-editor") {
                 let file            = this.filesService.get(message.filePath);
                 let editorComponent = this.getActiveEditorComponent();
                 let editor          = editorComponent.editor;
 
                 editorComponent.activeFile = file;
                 editor.setSession(file.session);
-            } else if (message.action == "close-tab") {
+            } else if (message.action === "close-tab") {
                 let file    = this.filesService.get(message.filePath);
                 let editors = this.editorsService.getEditorsAsArray();
 
@@ -135,11 +137,33 @@ export class EditorsComponent {
         });
 
         window.fs.onChangedFile(async (path: string) => {
-            console.log(path);
+            let message      = new ServiceMessage();
+            message.action   = "file-changed";
+            message.filePath = path;
+            this.tabsService.sendMessage(message);
         });
 
         window.fs.onDeletedFile(async (path: string) => {
+            let message      = new ServiceMessage();
+            message.action   = "file-deleted";
+            message.filePath = path;
+
+            this.tabsService.sendMessage(message);
+            this.filesService.sendMessage(message);
+        });
+
+        window.fs.onSavedFile(async (path: string) => {
+            let message      = new ServiceMessage();
+            message.action   = "file-saved";
+            message.filePath = path;
+
+            this.tabsService.sendMessage(message);
+        });
+
+        window.fs.onUpdateFilePath(async (path: string) => {
             console.log(path);
+            // this.tabsService.sendMessage(message);
+            // this.filesService.sendMessage(message);
         });
 
         window.main.onMenuActions(async (action: string) => {
diff --git a/src/app/editor/newton-editor/newton-editor.component.ts b/src/app/editor/newton-editor/newton-editor.component.ts
index d7505a4..04803a2 100644
--- a/src/app/editor/newton-editor/newton-editor.component.ts
+++ b/src/app/editor/newton-editor/newton-editor.component.ts
@@ -12,6 +12,7 @@ import "ace-builds/src-noconflict/theme-dracula";
 import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
 import { FilesModalService } from '../../common/services/editor/modals/files-modal.service';
 import { LSPService } from '../../common/services/lsp.service';
+import { TabsService } from '../../common/services/editor/tabs/tabs.service';
 import { EditorsService } from '../../common/services/editor/editors.service';
 
 import { NewtonEditorBase } from './newton-editor.base';
@@ -38,6 +39,7 @@ export class NewtonEditorComponent extends NewtonEditorBase {
         private infoBarService: InfoBarService,
         private editorsService: EditorsService,
         private lspService: LSPService,
+        private tabsService: TabsService,
         private filesModalService: FilesModalService
     ) {
         super();
@@ -124,6 +126,13 @@ export class NewtonEditorComponent extends NewtonEditorBase {
             this.editorsService.sendMessage(message);
         });
 
+        this.editor.on("change", () => {
+            let message      = new ServiceMessage();
+            message.action   = "file-changed";
+            message.filePath = this.activeFile.path;
+            this.tabsService.sendMessage(message);
+        });
+
         this.editor.on("changeSession", (session) => {
             this.lspService.registerEditor(this.editor);
             this.updateInfoBar();
diff --git a/src/app/editor/tabs/tabs.component.css b/src/app/editor/tabs/tabs.component.css
index 3a10c2a..99bd6d0 100644
--- a/src/app/editor/tabs/tabs.component.css
+++ b/src/app/editor/tabs/tabs.component.css
@@ -19,13 +19,25 @@
     border-right-style: solid;
     border-right-color: #ffffff64;
     border-right-width: 2px;
+}
 
+.active-tab {
+    background-color: rgba(255, 255, 255, 0.46);
+    color: rgba(255, 255, 255, 0.8);
 }
 
 .tab:hover {
     cursor: pointer;
 }
 
+.file-changed {
+    color: rgba(255, 168, 0, 0.64);
+}
+
+.file-deleted {
+    color: rgba(255, 0, 0, 0.64);
+}
+
 .title {
     margin-left: 2em;
     margin-right: 2em;
diff --git a/src/app/editor/tabs/tabs.component.ts b/src/app/editor/tabs/tabs.component.ts
index 9cf8f51..3a41154 100644
--- a/src/app/editor/tabs/tabs.component.ts
+++ b/src/app/editor/tabs/tabs.component.ts
@@ -42,9 +42,21 @@ export class TabsComponent {
     public ngAfterViewInit(): void {
         this.tabsService.getMessage$().pipe(
             takeUntil(this.unsubscribe)
-        ).subscribe((data: ServiceMessage) => {
-            if (data.action === "create-tab") {
-                this.createTab(data.fileName, data.fileUUID, data.filePath);
+        ).subscribe((message: ServiceMessage) => {
+            if (message.action === "create-tab") {
+                this.createTab(message.fileName, message.fileUUID, message.filePath);
+            } else if (message.action === "file-changed") {
+                let elm = document.querySelectorAll(`[title="${message.filePath}"]`)[1];
+                elm.classList.add("file-changed");
+                elm.classList.remove("file-deleted");
+            } else if (message.action === "file-deleted") {
+                let elm = document.querySelectorAll(`[title="${message.filePath}"]`)[1];
+                elm.classList.add("file-deleted");
+                elm.classList.remove("file-changed");
+            } else if (message.action === "file-saved") {
+                let elm = document.querySelectorAll(`[title="${message.filePath}"]`)[1];
+                elm.classList.remove("file-deleted");
+                elm.classList.remove("file-changed");
             }
         });
     }
diff --git a/src/polyfills.ts b/src/polyfills.ts
index 2ee3027..ffeaf57 100644
--- a/src/polyfills.ts
+++ b/src/polyfills.ts
@@ -32,6 +32,8 @@ declare global {
             closeFile: (arg0: any) => Promise<string>,
             getPathForFile: any,
             onLoadFiles: (arg0: any) => Promise<string>,
+            onUpdateFilePath: (arg0: any) => Promise<string>,
+            onSavedFile: (arg0: any) => Promise<string>,
             onChangedFile: (arg0: any) => Promise<string>,
             onDeletedFile: (arg0: any) => Promise<string>,
         }