This is an automated email from the ASF dual-hosted git repository.

mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 3b7a1b6af8 Issue #8366 : Do not ask to save restored Database SQL 
editors (#8423)
3b7a1b6af8 is described below

commit 3b7a1b6af847a581c53fb756e30b70ea33d4b7c1
Author: Matt Casters <[email protected]>
AuthorDate: Wed Sep 16 20:37:48 2026 +0200

    Issue #8366 : Do not ask to save restored Database SQL editors (#8423)
    
    Restored SQL editor tabs were marked dirty, so Hop Gui prompted to save
    them on close even when the user had not edited them in this session.
    Untitled session buffers now restore clean; named files that were dirty
    when Hop closed stay dirty. Loading the editor no longer flips the
    changed flag when the text is unchanged.
---
 .../perspective/database/DatabaseSqlEditorTab.java | 47 ++++++++++++++++++----
 .../perspective/database/DatabaseSqlTabMemory.java | 12 +++++-
 .../perspective/database/DatabaseWorkbench.java    | 37 +++++++++++------
 .../database/DatabaseSqlEditorTabTest.java         | 13 ++++++
 .../database/DatabaseSqlTabMemoryTest.java         | 22 ++++++++++
 5 files changed, 110 insertions(+), 21 deletions(-)

diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
index d21c223586..1e5c154952 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
@@ -99,6 +99,13 @@ public class DatabaseSqlEditorTab implements 
IHopFileTypeHandler {
   private String name;
   private boolean changed;
 
+  /**
+   * Editor text last considered clean (loaded, saved, or restored). Modify 
events that do not
+   * change this value are ignored so session restore and {@code 
setTextSuppressModify} cannot mark
+   * the tab dirty.
+   */
+  private String cleanText = "";
+
   /** True while Ctrl/Cmd+Enter is being handled so Traverse and KeyDown 
cannot both run SQL. */
   private boolean executeShortcutArmed;
 
@@ -124,12 +131,7 @@ public class DatabaseSqlEditorTab implements 
IHopFileTypeHandler {
     PropsUi.setLook(editorArea);
 
     editor = ContentEditorFacade.createContentEditor(editorArea, "sql");
-    editor.addModifyListener(
-        e -> {
-          setChanged();
-          host.updateGui(this);
-          workbench.schedulePersistSqlTabs();
-        });
+    editor.addModifyListener(e -> onEditorModified());
     editor.getControl().setData(DATA_SQL_TAB, this);
     editor
         .getControl()
@@ -151,6 +153,7 @@ public class DatabaseSqlEditorTab implements 
IHopFileTypeHandler {
 
   public void setInitialText(String sql) {
     editor.setTextSuppressModify(Const.NVL(sql, ""));
+    markClean();
   }
 
   public String getSqlText() {
@@ -544,6 +547,33 @@ public class DatabaseSqlEditorTab implements 
IHopFileTypeHandler {
     }
   }
 
+  /**
+   * Treat the current editor buffer as the saved/restored baseline so later 
close does not ask to
+   * save unless the user edits it.
+   */
+  public void markClean() {
+    if (editor != null && !editor.isDisposed()) {
+      cleanText = Const.NVL(editor.getText(), "");
+    }
+    clearChanged();
+  }
+
+  void onEditorModified() {
+    if (workbench.restoringSqlTabs) {
+      return;
+    }
+    if (!isModifiedFromClean(editor.getText(), cleanText)) {
+      return;
+    }
+    setChanged();
+    host.updateGui(this);
+    workbench.schedulePersistSqlTabs();
+  }
+
+  static boolean isModifiedFromClean(String editorText, String baseline) {
+    return !Objects.equals(Const.NVL(editorText, ""), Const.NVL(baseline, ""));
+  }
+
   @Override
   public Object getSubject() {
     return this;
@@ -593,7 +623,7 @@ public class DatabaseSqlEditorTab implements 
IHopFileTypeHandler {
         outputStream.write(editor.getText().getBytes(StandardCharsets.UTF_8));
         outputStream.flush();
       }
-      clearChanged();
+      markClean();
       host.updateGui(this);
       workbench.refreshTab(this);
       workbench.schedulePersistSqlTabs();
@@ -647,7 +677,7 @@ public class DatabaseSqlEditorTab implements 
IHopFileTypeHandler {
         String contents = IOUtils.toString(inputStream, 
StandardCharsets.UTF_8);
         editor.setTextSuppressModify(Const.NVL(contents, ""));
       }
-      clearChanged();
+      markClean();
     } catch (HopException e) {
       throw e;
     } catch (Exception e) {
@@ -657,6 +687,7 @@ public class DatabaseSqlEditorTab implements 
IHopFileTypeHandler {
 
   public void applyBuffer(String text, boolean markDirty) {
     editor.setTextSuppressModify(Const.NVL(text, ""));
+    cleanText = Const.NVL(editor.getText(), "");
     if (markDirty) {
       setChanged();
     } else {
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemory.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemory.java
index b509bc784d..ca1224da80 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemory.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemory.java
@@ -39,7 +39,8 @@ import org.eclipse.swt.widgets.Shell;
  * terminal tabs).
  *
  * <p>Query results are not persisted. Untitled or dirty buffers are stored as 
text (capped). Saved
- * clean files are reopened from VFS.
+ * clean files are reopened from VFS. Restored editors start clean so Hop Gui 
close does not ask to
+ * save a session buffer the user has not edited in this session.
  */
 final class DatabaseSqlTabMemory {
 
@@ -100,6 +101,7 @@ final class DatabaseSqlTabMemory {
         }
         workbench.selectSqlTabIndex(selectIndex);
       } finally {
+        workbench.markRestoredSqlTabsClean();
         workbench.restoringSqlTabs = false;
       }
     } catch (Exception e) {
@@ -360,4 +362,12 @@ final class DatabaseSqlTabMemory {
     return new Snapshot(
         tab.getDatabaseMeta().getName(), tab.getFilename(), sql, 
tab.hasChanged(), tab.getName());
   }
+
+  /**
+   * Untitled session buffers reopen clean (they are persisted as SQL, not as 
a file). A named file
+   * that was dirty when Hop closed stays dirty so the user can still save it.
+   */
+  static boolean restoredTabIsDirty(Snapshot snapshot) {
+    return snapshot != null && !Utils.isEmpty(snapshot.filename) && 
snapshot.dirty;
+  }
 }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
index a0395af582..4921db926a 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
@@ -1350,23 +1350,36 @@ public class DatabaseWorkbench extends Composite 
implements TabClosable {
     if (meta == null) {
       return false;
     }
-    if (!Utils.isEmpty(snapshot.filename) && !snapshot.dirty) {
-      openSqlFile(snapshot.filename, meta, null, false);
-      return true;
-    }
-    if (!Utils.isEmpty(snapshot.filename)) {
-      openSqlFile(snapshot.filename, meta, snapshot.sql, true);
-      return true;
+    boolean dirty = DatabaseSqlTabMemory.restoredTabIsDirty(snapshot);
+    DatabaseSqlEditorTab tab;
+    if (!Utils.isEmpty(snapshot.filename) && !dirty) {
+      tab = openSqlTab(meta, null, snapshot.filename, null, false);
+    } else if (!Utils.isEmpty(snapshot.filename)) {
+      tab = openSqlTab(meta, null, snapshot.filename, snapshot.sql, true);
+    } else {
+      tab = openSqlTab(meta, Const.NVL(snapshot.sql, ""), null, 
Const.NVL(snapshot.sql, ""), false);
+      if (!Utils.isEmpty(snapshot.name)) {
+        tab.setName(snapshot.name);
+      }
     }
-    DatabaseSqlEditorTab tab =
-        openSqlTab(
-            meta, Const.NVL(snapshot.sql, ""), null, Const.NVL(snapshot.sql, 
""), snapshot.dirty);
-    if (!Utils.isEmpty(snapshot.name)) {
-      tab.setName(snapshot.name);
+    if (!dirty) {
+      tab.markClean();
     }
     return true;
   }
 
+  void markRestoredSqlTabsClean() {
+    if (isDisposed()) {
+      return;
+    }
+    for (TabItemHandler item : items) {
+      if (item.getTypeHandler() instanceof DatabaseSqlEditorTab tab
+          && (Utils.isEmpty(tab.getFilename()) || !tab.hasChanged())) {
+        tab.markClean();
+      }
+    }
+  }
+
   void selectSqlTabIndex(int index) {
     int sqlIndex = 0;
     for (TabItemHandler item : items) {
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTabTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTabTest.java
index 155840f8c5..fcda5e7dd5 100644
--- 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTabTest.java
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTabTest.java
@@ -45,4 +45,17 @@ class DatabaseSqlEditorTabTest {
     assertEquals("Query 2: 50 row(s)", message);
     assertFalse(message.contains("capped"));
   }
+
+  @Test
+  void restoredOrLoadedTextIsNotAUserEdit() {
+    assertFalse(DatabaseSqlEditorTab.isModifiedFromClean("SELECT 1", "SELECT 
1"));
+    assertFalse(DatabaseSqlEditorTab.isModifiedFromClean(null, ""));
+    assertFalse(DatabaseSqlEditorTab.isModifiedFromClean("", null));
+  }
+
+  @Test
+  void typingAfterRestoreIsAUserEdit() {
+    assertTrue(DatabaseSqlEditorTab.isModifiedFromClean("SELECT 2", "SELECT 
1"));
+    assertTrue(DatabaseSqlEditorTab.isModifiedFromClean("SELECT 1", ""));
+  }
 }
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemoryTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemoryTest.java
index 7c50dbcab5..a89d7cd6af 100644
--- 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemoryTest.java
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlTabMemoryTest.java
@@ -74,6 +74,28 @@ class DatabaseSqlTabMemoryTest {
     assertEquals(0, DatabaseSqlTabMemory.number("nope", 0));
   }
 
+  @Test
+  void untitledSessionBuffersRestoreClean() {
+    DatabaseSqlTabMemory.Snapshot untitledDirty =
+        new DatabaseSqlTabMemory.Snapshot("warehouse", "", "SELECT 1", true, 
"SQL");
+    DatabaseSqlTabMemory.Snapshot untitledClean =
+        new DatabaseSqlTabMemory.Snapshot("warehouse", "", "SELECT 1", false, 
"SQL");
+    assertFalse(DatabaseSqlTabMemory.restoredTabIsDirty(untitledDirty));
+    assertFalse(DatabaseSqlTabMemory.restoredTabIsDirty(untitledClean));
+    assertFalse(DatabaseSqlTabMemory.restoredTabIsDirty(null));
+  }
+
+  @Test
+  void namedDirtyFilesStayDirtyAfterRestore() {
+    DatabaseSqlTabMemory.Snapshot dirtyFile =
+        new DatabaseSqlTabMemory.Snapshot(
+            "warehouse", "/tmp/load.sql", "SELECT 1", true, "load.sql");
+    DatabaseSqlTabMemory.Snapshot cleanFile =
+        new DatabaseSqlTabMemory.Snapshot("warehouse", "/tmp/load.sql", null, 
false, "load.sql");
+    assertTrue(DatabaseSqlTabMemory.restoredTabIsDirty(dirtyFile));
+    assertFalse(DatabaseSqlTabMemory.restoredTabIsDirty(cleanFile));
+  }
+
   private static java.util.Map<String, Object> originalEmptyConnection() {
     java.util.Map<String, Object> map = new java.util.LinkedHashMap<>();
     map.put(DatabaseSqlTabMemory.PROP_CONNECTION, "");

Reply via email to