Skip to content

Commit 17b0b99

Browse files
authored
Add periodic auto-save feature (#3031)
* Add periodic auto-save feature Closes #1376 * Rename event variable for clarity in auto-save * Prevent stale auto-save scheduling after periodic saves The PR review for #3031 requested that the periodic auto-save timer be reset after a successful auto-save cycle. This change follows the reviewer suggestion by scheduling _start_auto_save_timer with wx.CallAfter, while preserving the double-underscore event placeholder so translations remain intact. A focused regression test now verifies that the restart is scheduled after save completion. Constraint: Keep the fix scoped to the open PR #3031 review feedback Rejected: Restart the timer inline inside the EVT_TIMER callback | reviewer proposed deferred restart via wx.CallAfter Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep __ = event in _on_auto_save so the _() translation helper is not shadowed Tested: Targeted pytest for test_on_auto_save_restarts_timer_after_save; py_compile on touched files Not-tested: Full GUI-heavy utest/ui/test_mainframe.py module in this environment (wx access violation outside the targeted test)
1 parent 3e3fd26 commit 17b0b99

File tree

4 files changed

+75
-4
lines changed

4 files changed

+75
-4
lines changed

src/robotide/preferences/saving.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ def _create_editors(settings):
6868
IntegerChoiceEditor(settings, 'txt number of spaces', _('Separating spaces'),
6969
[str(i) for i in range(2, 11)],
7070
_('Number of spaces between cells when saving in txt format')
71+
),
72+
IntegerChoiceEditor(settings, 'auto save interval', _('Auto save interval (minutes)'),
73+
['0', '1', '2', '3', '5', '10', '15', '20', '30'],
74+
_('Automatically save all files after specified minutes (0 = disabled)')
7175
)
7276
]
7377

src/robotide/preferences/settings.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ reformat = False
2828
doc language = None
2929
# the style of the tabs in notebook pages, Edit, Text, Run. Values from 0 to 5.
3030
notebook theme = 0
31+
# auto save interval: Automatically save all files after specified minutes (0 = disabled)
32+
auto save interval = 0
3133

3234
[General]
3335
font size = 11
@@ -71,8 +73,6 @@ tc_kw_name = '#1A5FB4'
7173
variable = '#008080'
7274
background = '#F6F5F4'
7375
enable auto suggestions = True
74-
enable visible spaces = True
75-
enable visible newlines = True
7676

7777
[Grid]
7878
font size = 10

src/robotide/ui/mainframe.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ def __init__(self, application, controller):
167167
self.color_foreground = self.general_settings.get('foreground', '#5E5C64') if self.general_settings else '#5E5C64'
168168
self.font_face = self.general_settings.get('font face', '') if self.general_settings else ''
169169
self.font_size = self.general_settings.get('font size', 11) if self.general_settings else 11
170-
self.ui_language = self.general_settings.get('ui language', 'English') if self.general_settings else 'English'
170+
self.ui_language = self.general_settings.get('ui language', 'English') if self.general_settings else 'English'
171+
self._auto_save_interval = application.settings.get('auto save interval', 0)
172+
self._auto_save_timer = None
171173
self.main_menu = None
172174
self._init_ui()
173175
self.SetIcon(wx.Icon(self._image_provider.RIDE_ICON))
@@ -187,6 +189,7 @@ def __init__(self, application, controller):
187189
self.Bind(aui.EVT_AUI_PANE_DOCKING, self.OnFloatDock)
188190
self.Bind(aui.EVT_AUI_PANE_DOCKED, self.OnFloatDock)
189191
self._subscribe_messages()
192+
self._start_auto_save_timer()
190193
wx.CallAfter(self.actions.register_tools) # DEBUG
191194
# DEBUG wx.CallAfter(self.OnSettingsChanged, self.general_settings)
192195

@@ -197,7 +200,8 @@ def _subscribe_messages(self):
197200
(self._set_label, RideTreeSelection),
198201
(self._show_validation_error, RideInputValidationError),
199202
(self._show_modification_prevented_error, RideModificationPrevented),
200-
(self.on_ui_language_changed, RideSettingsChanged)
203+
(self.on_ui_language_changed, RideSettingsChanged),
204+
(self._on_auto_save_settings_changed, RideSettingsChanged)
201205
]:
202206
PUBLISHER.subscribe(listener, topic)
203207

@@ -364,6 +368,31 @@ def get_selected_datafile(self):
364368
def get_selected_datafile_controller(self):
365369
return self.tree.get_selected_datafile_controller()
366370

371+
def _start_auto_save_timer(self):
372+
"""Start the auto-save timer if interval is set."""
373+
if self._auto_save_timer:
374+
self._auto_save_timer.Stop()
375+
self._auto_save_timer = None
376+
if self._auto_save_interval > 0:
377+
self._auto_save_timer = wx.Timer(self)
378+
self.Bind(wx.EVT_TIMER, self._on_auto_save, self._auto_save_timer)
379+
self._auto_save_timer.Start(self._auto_save_interval * 60 * 1000) # minutes to milliseconds
380+
381+
def _on_auto_save(self, event):
382+
"""Auto-save all files when timer fires."""
383+
__ = event
384+
if self.controller and self.controller.is_dirty():
385+
RideBeforeSaving().publish()
386+
self.save_all()
387+
self.SetStatusText(_('Auto-saved all files'))
388+
wx.CallAfter(self._start_auto_save_timer)
389+
390+
def _on_auto_save_settings_changed(self, message):
391+
"""Update auto-save timer when settings change."""
392+
if message.keys and 'auto save interval' in message.keys:
393+
self._auto_save_interval = self._application.settings.get('auto save interval', 0)
394+
self._start_auto_save_timer()
395+
367396
def on_close(self, event):
368397
from ..preferences import RideSettings
369398
if self._allowed_to_exit():

utest/ui/test_mainframe.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,44 @@ def my_call(*argument, **options):
171171
for _ in range(16): # Hoping to cover all 4 cases
172172
start_external_app(__file__)
173173

174+
def test_on_auto_save_restarts_timer_after_save(self):
175+
calls = []
176+
177+
class DirtyController:
178+
@staticmethod
179+
def is_dirty():
180+
return True
181+
182+
class FakeBeforeSaving:
183+
def publish(self):
184+
calls.append("publish")
185+
186+
def restart_timer():
187+
calls.append("restart")
188+
189+
def fake_call_after(callback, *args, **kwargs):
190+
calls.append(("callafter", callback, args, kwargs))
191+
192+
self.frame.controller = DirtyController()
193+
with MonkeyPatch().context() as m:
194+
m.setattr(mainframe, 'RideBeforeSaving', FakeBeforeSaving)
195+
m.setattr(wx, 'CallAfter', fake_call_after)
196+
m.setattr(self.frame, 'save_all', lambda: calls.append("save_all"))
197+
m.setattr(self.frame, 'SetStatusText', lambda text: calls.append(("status", text)))
198+
m.setattr(self.frame, '_start_auto_save_timer', restart_timer)
199+
200+
self.frame._on_auto_save(object())
201+
202+
self.assertEqual(
203+
calls,
204+
[
205+
"publish",
206+
"save_all",
207+
("status", "Auto-saved all files"),
208+
("callafter", restart_timer, (), {}),
209+
],
210+
)
211+
174212

175213
if __name__ == '__main__':
176214
unittest.main()

0 commit comments

Comments
 (0)