Coverage for src/robotide/recentfiles/recentfiles.py: 55%
108 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
1# Copyright 2008-2015 Nokia Networks
2# Copyright 2016- Robot Framework Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16import builtins
17import os.path
19import wx
21from ..pluginapi import Plugin
22from ..action import ActionInfo, SeparatorInfo
23from ..publish import RideOpenSuite, RideFileNameChanged
24from ..publish.messages import RideNewProject, RideSaved
26_ = wx.GetTranslation # To keep linter/code analyser happy
27builtins.__dict__['_'] = wx.GetTranslation
30def normalize_path(path):
31 if os.path.basename(path).startswith('__init__.'): 31 ↛ 32line 31 didn't jump to line 32 because the condition on line 31 was never true
32 return os.path.dirname(path)
33 return os.path.abspath(path)
36def remove_non_existing_paths(paths):
37 return [f for f in paths if os.path.exists(f)]
40class RecentFilesPlugin(Plugin):
41 __doc__ = _("""Add recently opened files to the file menu.""")
43 def __init__(self, application=None):
44 settings = {'recent_files': [], 'max_number_of_files': 4}
45 Plugin.__init__(self, application, default_settings=settings)
46 self.recent_files = remove_non_existing_paths(self.recent_files)
47 self._new_project_path = None
49 def enable(self):
50 self._save_currently_loaded_suite()
51 self._add_recent_files_to_menu()
52 self._new_project_path = None
53 self.subscribe(self.on_suite_opened, RideOpenSuite)
54 self.subscribe(self.on_file_name_changed, RideFileNameChanged)
55 self.subscribe(self.on_new_project_opened, RideNewProject)
56 self.subscribe(self.on_saved, RideSaved)
58 def disable(self):
59 self.unregister_actions()
60 self.unsubscribe_all()
62 def on_suite_opened(self, message):
63 # Update menu with CallAfter to ensure ongoing menu selection
64 # handling has finished before menu is changed
65 wx.CallAfter(self._add_to_recent_files, message.path)
66 self._new_project_path = None
68 def on_file_name_changed(self, message):
69 self._new_project_path = None
70 if not message.old_filename:
71 return
72 old_filename = normalize_path(message.old_filename)
73 new_filename = normalize_path(message.datafile.filename)
74 if old_filename not in self.recent_files:
75 return
76 index = self.recent_files.index(old_filename)
77 self.recent_files[index] = new_filename
78 self._save_settings_and_update_file_menu()
80 def on_new_project_opened(self, message):
81 self._new_project_path = message.path
83 def on_saved(self, message):
84 _ = message
85 if self._new_project_path is not None:
86 wx.CallAfter(self._add_to_recent_files, self._new_project_path)
87 self._new_project_path = None
89 def _get_file_menu(self):
90 menubar = self.get_menu_bar()
91 pos = menubar.FindMenu('File')
92 file_menu = menubar.GetMenu(pos)
93 return file_menu
95 def _save_currently_loaded_suite(self):
96 model = self.model
97 if model and model.suite: 97 ↛ 98line 97 didn't jump to line 98 because the condition on line 97 was never true
98 self._add_to_recent_files(model.suite.source)
100 def _add_to_recent_files(self, path):
101 if not path:
102 return
103 path = normalize_path(path)
104 if path in self.recent_files:
105 self.recent_files.remove(path)
106 self.recent_files.insert(0, path)
107 self._save_settings_and_update_file_menu()
109 def _save_settings_and_update_file_menu(self):
110 self.recent_files = remove_non_existing_paths(self.recent_files)
111 self.recent_files = self.recent_files[:self.max_number_of_files]
112 self.save_setting('recent_files', self.recent_files)
113 self.unregister_actions()
114 self._add_recent_files_to_menu()
116 def _add_recent_files_to_menu(self):
117 if not self.recent_files: 117 ↛ 118line 117 didn't jump to line 118 because the condition on line 117 was never true
118 action = ActionInfo(_('File'), _('No recent files'))
119 action.set_menu_position(before=_('Exit'))
120 self.register_action(action)
121 else:
122 for n, path in enumerate(self.recent_files):
123 self._add_file_to_menu(path, n)
124 sep = SeparatorInfo(_('File'))
125 sep.set_menu_position(before=_('Exit'))
126 self.register_action(sep)
128 def _add_file_to_menu(self, path, n):
129 entry = RecentFileEntry(n+1, path, self)
130 self.register_action(entry.get_action_info())
133class RecentFileEntry(object):
135 def __init__(self, index, file, plugin):
136 self.file = file
137 self.index = index
138 self.path = normalize_path(self.file)
139 self.filename = os.path.basename(file)
140 self.plugin = plugin
141 self.label = '&%s: %s' % (index, self.filename)
142 self.doc = _('Open %s') % self.path
144 def on_open_recent(self, event):
145 __ = event
146 if not self.plugin.frame.check_unsaved_modifications():
147 return
148 self.plugin.open_suite(self.path)
150 def get_action_info(self):
151 action_info = ActionInfo(_('File'), self.label, self.on_open_recent,
152 doc=self.doc)
153 action_info.set_menu_position(before=_('Exit'))
154 return action_info