Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions lib/features/extensions/extension_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,26 @@ class _ExtensionSqlWorkspaceState
if (index < 0 || index >= _sessions.length) return;
if (_sessions.length <= 1) return;
final session = _sessions[index];
if (session.stagingBuffer != null && session.stagingBuffer!.isDirty) {
if (session.isDirty) {
final hasDirtyStaging =
session.stagingBuffer != null && session.stagingBuffer!.isDirty;
final hasUnsavedText = session.isModified ||
(session.filePath == null && session.controller.text.trim().isNotEmpty);
final String message;
if (hasDirtyStaging && hasUnsavedText) {
message =
'This query tab contains unsaved query text and staged database changes. Closing the tab will discard them.';
} else if (hasDirtyStaging) {
message =
'This query tab contains staged database changes that have not been applied yet. Closing the tab will discard these changes.';
} else {
message =
'This query tab contains unsaved SQL query text. Closing the tab will discard your changes.';
}
final confirmed = await showUnsavedTabChangesDialog(
context: context,
tabTitle: session.title,
message: message,
);
if (confirmed != true) return;
}
Expand Down Expand Up @@ -391,7 +407,7 @@ class _ExtensionSqlWorkspaceState
selection: material.TextSelection.collapsed(offset: text.length),
);
session.title = file.name;
session.filePath = file.path;
session.markSaved(newFilePath: file.path);
setState(() {});
} else {
_addNewTab(initialSql: text, title: file.name, filePath: file.path);
Expand All @@ -412,6 +428,7 @@ class _ExtensionSqlWorkspaceState
final existingPath = session.filePath;
if (existingPath != null && existingPath.isNotEmpty) {
await File(existingPath).writeAsString(session.controller.text);
session.markSaved();
if (!mounted) return;
showAppToast(
context: context,
Expand All @@ -435,8 +452,8 @@ class _ExtensionSqlWorkspaceState
await File(path).writeAsString(session.controller.text);
if (!mounted) return;
setState(() {
session.filePath = path;
session.title = File(path).uri.pathSegments.last;
session.markSaved(newFilePath: path);
});
showAppToast(
context: context,
Expand Down
23 changes: 20 additions & 3 deletions lib/features/mysql/mysql_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,26 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
if (index < 0 || index >= _sessions.length) return;
if (_sessions.length <= 1) return;
final session = _sessions[index];
if (session.stagingBuffer != null && session.stagingBuffer!.isDirty) {
if (session.isDirty) {
final hasDirtyStaging =
session.stagingBuffer != null && session.stagingBuffer!.isDirty;
final hasUnsavedText = session.isModified ||
(session.filePath == null && session.controller.text.trim().isNotEmpty);
final String message;
if (hasDirtyStaging && hasUnsavedText) {
message =
'This query tab contains unsaved query text and staged database changes. Closing the tab will discard them.';
} else if (hasDirtyStaging) {
message =
'This query tab contains staged database changes that have not been applied yet. Closing the tab will discard these changes.';
} else {
message =
'This query tab contains unsaved SQL query text. Closing the tab will discard your changes.';
}
final confirmed = await showUnsavedTabChangesDialog(
context: context,
tabTitle: session.title,
message: message,
);
if (confirmed != true) return;
}
Expand Down Expand Up @@ -588,7 +604,7 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
selection: material.TextSelection.collapsed(offset: text.length),
);
session.title = file.name;
session.filePath = file.path;
session.markSaved(newFilePath: file.path);
setState(() {});
} else {
_addNewTab(initialSql: text, title: file.name, filePath: file.path);
Expand All @@ -609,6 +625,7 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
final existingPath = session.filePath;
if (existingPath != null && existingPath.isNotEmpty) {
await File(existingPath).writeAsString(session.controller.text);
session.markSaved();
if (!mounted) return;
showAppToast(
context: context,
Expand All @@ -632,8 +649,8 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
await File(path).writeAsString(session.controller.text);
if (!mounted) return;
setState(() {
session.filePath = path;
session.title = File(path).uri.pathSegments.last;
session.markSaved(newFilePath: path);
});
showAppToast(
context: context,
Expand Down
23 changes: 20 additions & 3 deletions lib/features/postgresql/postgres_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,26 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
if (index < 0 || index >= _sessions.length) return;
if (_sessions.length <= 1) return;
final session = _sessions[index];
if (session.stagingBuffer != null && session.stagingBuffer!.isDirty) {
if (session.isDirty) {
final hasDirtyStaging =
session.stagingBuffer != null && session.stagingBuffer!.isDirty;
final hasUnsavedText = session.isModified ||
(session.filePath == null && session.controller.text.trim().isNotEmpty);
final String message;
if (hasDirtyStaging && hasUnsavedText) {
message =
'This query tab contains unsaved query text and staged database changes. Closing the tab will discard them.';
} else if (hasDirtyStaging) {
message =
'This query tab contains staged database changes that have not been applied yet. Closing the tab will discard these changes.';
} else {
message =
'This query tab contains unsaved SQL query text. Closing the tab will discard your changes.';
}
final confirmed = await showUnsavedTabChangesDialog(
context: context,
tabTitle: session.title,
message: message,
);
if (confirmed != true) return;
}
Expand Down Expand Up @@ -712,7 +728,7 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
selection: material.TextSelection.collapsed(offset: text.length),
);
session.title = file.name;
session.filePath = file.path;
session.markSaved(newFilePath: file.path);
setState(() {});
} else {
_addNewTab(initialSql: text, title: file.name, filePath: file.path);
Expand All @@ -733,6 +749,7 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
final existingPath = session.filePath;
if (existingPath != null && existingPath.isNotEmpty) {
await File(existingPath).writeAsString(session.controller.text);
session.markSaved();
if (!mounted) return;
showAppToast(
context: context,
Expand All @@ -756,8 +773,8 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
await File(path).writeAsString(session.controller.text);
if (!mounted) return;
setState(() {
session.filePath = path;
session.title = File(path).uri.pathSegments.last;
session.markSaved(newFilePath: path);
});
showAppToast(
context: context,
Expand Down
23 changes: 20 additions & 3 deletions lib/features/sqlite/sqlite_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,26 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
if (index < 0 || index >= _sessions.length) return;
if (_sessions.length <= 1) return;
final session = _sessions[index];
if (session.stagingBuffer != null && session.stagingBuffer!.isDirty) {
if (session.isDirty) {
final hasDirtyStaging =
session.stagingBuffer != null && session.stagingBuffer!.isDirty;
final hasUnsavedText = session.isModified ||
(session.filePath == null && session.controller.text.trim().isNotEmpty);
final String message;
if (hasDirtyStaging && hasUnsavedText) {
message =
'This query tab contains unsaved query text and staged database changes. Closing the tab will discard them.';
} else if (hasDirtyStaging) {
message =
'This query tab contains staged database changes that have not been applied yet. Closing the tab will discard these changes.';
} else {
message =
'This query tab contains unsaved SQL query text. Closing the tab will discard your changes.';
}
final confirmed = await showUnsavedTabChangesDialog(
context: context,
tabTitle: session.title,
message: message,
);
if (confirmed != true) return;
}
Expand Down Expand Up @@ -547,7 +563,7 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
selection: material.TextSelection.collapsed(offset: text.length),
);
session.title = file.name;
session.filePath = file.path;
session.markSaved(newFilePath: file.path);
setState(() {});
} else {
_addNewTab(initialSql: text, title: file.name, filePath: file.path);
Expand All @@ -568,6 +584,7 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
final existingPath = session.filePath;
if (existingPath != null && existingPath.isNotEmpty) {
await File(existingPath).writeAsString(session.controller.text);
session.markSaved();
if (!mounted) return;
showAppToast(
context: context,
Expand All @@ -591,8 +608,8 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
await File(path).writeAsString(session.controller.text);
if (!mounted) return;
setState(() {
session.filePath = path;
session.title = File(path).uri.pathSegments.last;
session.markSaved(newFilePath: path);
});
showAppToast(
context: context,
Expand Down
6 changes: 4 additions & 2 deletions lib/features/workspace/sql_query_tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class SqlQueryTabBar extends material.StatelessWidget {
Future<bool?> showUnsavedTabChangesDialog({
required material.BuildContext context,
required String tabTitle,
String? message,
}) {
return showAppDialog<bool>(
context: context,
Expand All @@ -73,8 +74,9 @@ Future<bool?> showUnsavedTabChangesDialog({
children: [
Text('Unsaved Changes in "$tabTitle"').semiBold().large(),
const Gap(8),
const Text(
'This query tab contains staged database changes that have not been applied yet. Closing the tab will discard these changes.',
Text(
message ??
'This query tab contains staged database changes that have not been applied yet. Closing the tab will discard these changes.',
).muted().small(),
const Gap(20),
material.Align(
Expand Down
37 changes: 33 additions & 4 deletions lib/features/workspace/sql_query_tab_session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ class SqlQueryTabSession {
this.filePath,
double initialFraction = 0.65,
}) : controller = material.TextEditingController(text: initialSql ?? ''),
_savedSql = initialSql ?? '',
topFraction = material.ValueNotifier<double>(initialFraction) {
controller.addListener(_onTextChanged);
UnsavedWorkRegistry.instance.register(
this,
() => isModified || (stagingBuffer?.isDirty ?? false),
() => isDirty,
);
}

Expand All @@ -28,6 +30,7 @@ class SqlQueryTabSession {
final material.TextEditingController controller;
final material.ValueNotifier<double> topFraction;

String _savedSql;
bool running = false;
String? error;
List<String> columns = const [];
Expand All @@ -37,7 +40,34 @@ class SqlQueryTabSession {
DataGridStagingBuffer? stagingBuffer;
String? lastExecutedSql;
bool savingChanges = false;
bool isModified = false;
bool? _manualModified;

/// True if the SQL query text has unsaved changes or was manually marked modified.
bool get isModified =>
_manualModified ??
(filePath != null
? controller.text != _savedSql
: controller.text.trim().isNotEmpty);

set isModified(bool value) {
_manualModified = value;
}

void _onTextChanged() {
_manualModified = null;
}

/// True if the tab has uncommitted changes (modified text, unsaved query draft, or dirty staging buffer).
bool get isDirty => isModified || (stagingBuffer?.isDirty ?? false);

/// Marks the current query text as saved to disk or loaded from file.
void markSaved({String? newFilePath}) {
_savedSql = controller.text;
_manualModified = false;
if (newFilePath != null) {
filePath = newFilePath;
}
}

/// PK columns for SQL-grid Save, empty when Save is disabled.
List<String> resultGridPrimaryKeys = const [];
Expand All @@ -51,15 +81,14 @@ class SqlQueryTabSession {
text: next,
selection: material.TextSelection.collapsed(offset: next.length),
);
isModified = true;
}

void clearSql() {
controller.clear();
isModified = true;
}

void dispose() {
controller.removeListener(_onTextChanged);
UnsavedWorkRegistry.instance.unregister(this);
controller.dispose();
topFraction.dispose();
Expand Down
Loading
Loading