You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by mc...@apache.org on 2026/09/16 18:37:53 UTC
(hop) branch main updated: Issue #8366 : Do not ask to save restored Database SQL editors (#8423)
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 <ma...@gmail.com>
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, "");