Skip to content

Commit cff8f22

Browse files
authored
feat: Ask user to save unsaved changes on tab/app closed #289 from vyshnav-vinod/feat/ask-save-before-close (nightly)
feat: Ask user to save the file before they close the tab #280
2 parents 36ae81a + 30a251c commit cff8f22

File tree

6 files changed

+59
-5
lines changed

6 files changed

+59
-5
lines changed

biscuit/core/commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def close_dir(self, *_) -> None:
9090
self.base.close_active_directory()
9191

9292
def quit(self, *_) -> None:
93+
self.base.on_close_app()
9394
self.base.destroy()
9495

9596
def toggle_maximize(self, *_) -> None:

biscuit/core/components/editors/texteditor/__init__.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import tkinter as tk
4+
from hashlib import md5
45
from tkinter.font import Font
56

67
from biscuit.core.utils import Scrollbar
@@ -24,7 +25,8 @@ def __init__(self, master, path=None, exists=True, language=None, minimalist=Fal
2425
self.editable = True
2526
self.run_command_value = None
2627
self.unsupported = False
27-
28+
self.content_hash = ''
29+
2830
if not self.standalone:
2931
self.__buttons__ = [('sync', self.base.editorsmanager.reopen_active_editor),]
3032

@@ -81,6 +83,29 @@ def __init__(self, master, path=None, exists=True, language=None, minimalist=Fal
8183
if self.base.settings.config.auto_save_enabled:
8284
self.auto_save()
8385

86+
def file_loaded(self):
87+
self.recalculate_content_hash()
88+
self.event_generate("<<FileLoaded>>", when="tail")
89+
90+
def recalculate_content_hash(self):
91+
""" Recalculate the hash of the editor content """
92+
93+
self.content_hash = self.calculate_content_hash()
94+
95+
def calculate_content_hash(self):
96+
""" Calculate the hash of the editor content """
97+
98+
if self.exists and self.editable:
99+
text = self.text.get_all_text()
100+
return md5(text.encode()).hexdigest()
101+
102+
@property
103+
def unsaved_changes(self):
104+
""" Check if the editor content has changed """
105+
106+
if self.editable:
107+
return self.content_hash != self.calculate_content_hash()
108+
84109
def run_file(self, dedicated=False, external=False):
85110
if not self.run_command_value:
86111
self.base.notifications.show("No programs are configured to run this file.")
@@ -137,6 +162,7 @@ def set_fontsize(self, size):
137162

138163
def save(self, path=None):
139164
if self.editable:
165+
self.recalculate_content_hash()
140166
self.text.save_file(path)
141167

142168
def auto_save(self):

biscuit/core/components/editors/texteditor/text.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Text(BaseText):
3232

3333
def __init__(self, master: TextEditor, path: str=None, exists: bool=True, minimalist: bool=False, standalone: bool=False, language: str=None, *args, **kwargs) -> None:
3434
super().__init__(master, *args, **kwargs)
35-
self.master = master
35+
self.master: TextEditor = master
3636
self.path = path
3737
self.filename = os.path.basename(path) if path else None
3838
self.encoding = 'utf-8'
@@ -901,7 +901,7 @@ def process_queue(self, eol: str=None):
901901
# If the queue is empty, schedule the next check after a short delay
902902
self.master.after(100, self.process_queue)
903903

904-
self.master.event_generate("<<FileLoaded>>", when="tail")
904+
self.master.file_loaded()
905905

906906
def custom_get(self, start, end):
907907
content = self.get(start, end)

biscuit/core/gui.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def setup_tk(self) -> None:
6060

6161
self.setup_floating_widgets()
6262
self.setup_root()
63+
64+
self.protocol("WM_DELETE_WINDOW", self.on_close_app)
6365

6466
def setup_root(self):
6567
# the very parent of all GUI parts
@@ -137,6 +139,9 @@ def on_gui_update(self, *_) -> None:
137139

138140
def register_onfocus(self, fn) -> None:
139141
self.onfocus_callbacks.append(fn)
142+
143+
def on_close_app(self) -> None:
144+
self.editorsmanager.delete_all_editors()
140145

141146
def on_focus(self, *_) -> None:
142147
for fn in self.onfocus_callbacks:

biscuit/core/layout/base/content/editors/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,23 @@ def generate_actionsets(self) -> None:
7474

7575
def add_default_editors(self) -> None:
7676
"Adds all default editors"
77+
7778
self.add_editors(self.default_editors)
7879

7980
def add_welcome(self) -> None:
8081
"Shows welcome tab"
82+
8183
self.add_editor(Welcome(self))
8284

8385
def add_editors(self, editors: list[Editor]) -> None:
8486
"Append <Editor>s to list. Create tabs for them."
87+
8588
for editor in editors:
8689
self.add_editor(editor)
8790

8891
def add_editor(self, editor: Union[Editor,BaseEditor]) -> Editor | BaseEditor:
8992
"Appends a editor to list. Create a tab."
93+
9094
self.active_editors.append(editor)
9195
if editor.content:
9296
editor.content.create_buttons(self.editorsbar.container)
@@ -97,9 +101,12 @@ def add_editor(self, editor: Union[Editor,BaseEditor]) -> Editor | BaseEditor:
97101

98102
def delete_all_editors(self) -> None:
99103
"Permanently delete all editors."
100-
for editor in self.active_editors:
101-
editor.destroy()
102104

105+
for tab in self.tabs.tabs:
106+
if e := tab.editor:
107+
self.tabs.save_unsaved_changes(e)
108+
e.destroy()
109+
103110
self.editorsbar.clear()
104111
self.tabs.clear_all_tabs()
105112
self.active_editors.clear()
@@ -108,6 +115,7 @@ def delete_all_editors(self) -> None:
108115

109116
def reopen_active_editor(self) -> None:
110117
"Reopen the active editor"
118+
111119
if self.active_editor and self.active_editor.exists:
112120
self.delete_editor(self.active_editor)
113121
self.update()

biscuit/core/layout/base/content/editors/tabs.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from . import Editorsbar
77
from biscuit.core.components import Editor
88

9+
import os
910
import tkinter as tk
11+
from tkinter.messagebox import askyesno
1012

1113
from biscuit.core.utils import Frame
1214

@@ -30,8 +32,20 @@ def add_tab(self, editor: Editor) -> None:
3032

3133
def close_active_tab(self) -> None:
3234
self.close_tab(self.active_tab)
35+
36+
def save_unsaved_changes(self, e) -> None:
37+
if e.content and e.content.editable and e.content.unsaved_changes:
38+
if askyesno(f"Unsaved changes", f"Do you want to save the changes you made to {e.filename}"):
39+
if e.exists:
40+
e.save()
41+
else:
42+
self.base.commands.save_as()
43+
print(f"Saved changes to {e.path}.")
3344

3445
def close_tab(self, tab: Tab) -> None:
46+
if e := tab.editor:
47+
self.save_unsaved_changes(e)
48+
3549
try:
3650
i = self.tabs.index(tab)
3751
except ValueError:

0 commit comments

Comments
 (0)