diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py index 1f02d422104..6598def958f 100644 --- a/spyder/plugins/ipythonconsole.py +++ b/spyder/plugins/ipythonconsole.py @@ -51,7 +51,7 @@ from spyder.utils.ipython.style import create_qss_style from spyder.utils.qthelpers import create_action, MENU_SEPARATOR from spyder.utils import icon_manager as ima -from spyder.utils import encoding, programs +from spyder.utils import encoding, programs, sourcecode from spyder.utils.misc import get_error_match, remove_backslashes from spyder.widgets.findreplace import FindReplace from spyder.widgets.ipythonconsole import ClientWidget @@ -603,6 +603,7 @@ def __init__(self, parent, testing=False): self.master_clients = 0 self.clients = [] + self.filenames = [] self.mainwindow_close = False self.create_new_client_if_empty = True self.testing = testing @@ -736,6 +737,7 @@ def refresh_plugin(self): sw = client.shellwidget self.variableexplorer.set_shellwidget_from_id(id(sw)) self.help.set_shell(sw) + self.update_tabs_text() self.sig_update_plugin_title.emit() def get_plugin_actions(self): @@ -901,7 +903,9 @@ def write_to_stdin(self, line): @Slot() @Slot(bool) - def create_new_client(self, give_focus=True): + @Slot(str) + @Slot(bool, str) + def create_new_client(self, give_focus=True, filename=''): """Create a new client""" self.master_clients += 1 client_id = dict(int_id=to_text_string(self.master_clients), @@ -914,7 +918,7 @@ def create_new_client(self, give_focus=True): interpreter_versions=self.interpreter_versions(), connection_file=cf, menu_actions=self.menu_actions) - self.add_tab(client, name=client.get_name()) + self.add_tab(client, name=client.get_name(), filename=filename) if cf is None: error_msg = _("The directory {} is not writable and it is " @@ -1186,8 +1190,10 @@ def close_client(self, index=None, client=None, force=False): # Note: client index may have changed after closing related widgets self.tabwidget.removeTab(self.tabwidget.indexOf(client)) self.clients.remove(client) + self.filenames.pop(index) if not self.tabwidget.count() and self.create_new_client_if_empty: self.create_new_client() + self.update_tabs_text() self.sig_update_plugin_title.emit() def get_client_index_from_id(self, client_id): @@ -1259,7 +1265,7 @@ def create_client_from_path(self, path): def create_client_for_file(self, filename): """Create a client to execute code related to a file.""" # Create client - self.create_new_client() + self.create_new_client(filename=filename) # Don't increase the count of master clients self.master_clients -= 1 @@ -1267,7 +1273,8 @@ def create_client_for_file(self, filename): # Rename client tab with filename client = self.get_current_client() client.allow_rename = False - self.rename_client_tab(client, filename) + tab_text = self.disambiguate_fname(filename) + self.rename_client_tab(client, tab_text) def get_client_for_file(self, filename): """Get client associated with a given file.""" @@ -1349,25 +1356,42 @@ def restart_kernel(self): client.restart_kernel() #------ Public API (for tabs) --------------------------------------------- - def add_tab(self, widget, name): + def add_tab(self, widget, name, filename=''): """Add tab""" self.clients.append(widget) index = self.tabwidget.addTab(widget, name) + self.filenames.insert(index, filename) self.tabwidget.setCurrentIndex(index) if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() self.activateWindow() widget.get_control().setFocus() + self.update_tabs_text() def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ + filename = self.filenames.pop(index_from) client = self.clients.pop(index_from) + self.filenames.insert(index_to, filename) self.clients.insert(index_to, client) self.sig_update_plugin_title.emit() + def disambiguate_fname(self, fname): + """Generate a file name without ambiguation.""" + files_path_list = [filename for filename in self.filenames + if filename] + return sourcecode.disambiguate_fname(files_path_list, fname) + + def update_tabs_text(self): + """Update the text from the tabs.""" + for index, fname in enumerate(self.filenames): + if fname: + client = self.clients[index] + self.rename_client_tab(client, self.disambiguate_fname(fname)) + def rename_client_tab(self, client, given_name): """Rename client's tab""" index = self.get_client_index_from_id(id(client)) diff --git a/spyder/plugins/tests/test_ipythonconsole.py b/spyder/plugins/tests/test_ipythonconsole.py index 3c5efba939e..f262de4aa24 100644 --- a/spyder/plugins/tests/test_ipythonconsole.py +++ b/spyder/plugins/tests/test_ipythonconsole.py @@ -35,6 +35,7 @@ #============================================================================== SHELL_TIMEOUT = 20000 PYQT_WHEEL = PYQT_VERSION > '5.6' +TEMP_DIRECTORY = tempfile.gettempdir() #============================================================================== @@ -76,6 +77,40 @@ def close_console(): #============================================================================== # Tests #============================================================================== +@flaky(max_runs=3) +def test_console_disambiguation(ipyconsole, qtbot): + """Test the disambiguation of dedicated consoles.""" + # Create directories and file for TEMP_DIRECTORY/a/b/c.py + # and TEMP_DIRECTORY/a/d/c.py + dir_b = osp.join(TEMP_DIRECTORY, 'a', 'b') + filename_b = osp.join(dir_b, 'c.py') + if not osp.isdir(dir_b): + os.makedirs(dir_b) + if not osp.isfile(filename_b): + file_c = open(filename_b, 'w+') + file_c.close() + dir_d = osp.join(TEMP_DIRECTORY, 'a', 'd') + filename_d = osp.join(dir_d, 'c.py') + if not osp.isdir(dir_d): + os.makedirs(dir_d) + if not osp.isfile(filename_d): + file_e = open(filename_d, 'w+') + file_e.close() + + # Create new client and assert name without disambiguation + ipyconsole.create_client_for_file(filename_b) + client = ipyconsole.get_current_client() + assert client.get_name() == 'c.py/A' + + # Create new client and assert name with disambiguation + ipyconsole.create_client_for_file(filename_d) + client = ipyconsole.get_current_client() + assert client.get_name() == 'c.py - d/A' + ipyconsole.tabwidget.setCurrentIndex(1) + client = ipyconsole.get_current_client() + assert client.get_name() == 'c.py - b/A' + + @flaky(max_runs=3) def test_console_coloring(ipyconsole, qtbot): @@ -99,6 +134,7 @@ def test_console_coloring(ipyconsole, qtbot): assert console_background_color.strip() == editor_background_color.strip() assert console_font_color.strip() == editor_font_color.strip() + @flaky(max_runs=3) @pytest.mark.skipif(os.name == 'nt', reason="It doesn't work on Windows") def test_get_env(ipyconsole, qtbot): diff --git a/spyder/utils/sourcecode.py b/spyder/utils/sourcecode.py index 367a83a2736..f3816ce50bd 100644 --- a/spyder/utils/sourcecode.py +++ b/spyder/utils/sourcecode.py @@ -183,7 +183,7 @@ def differentiate_prefix(path_components0, path_components1): path_0 = '/' return path_0 -def get_file_title(files_path_list, filename): +def disambiguate_fname(files_path_list, filename): """Get tab title without ambiguation.""" fname = os.path.basename(filename) same_name_files = get_same_name_files(files_path_list, fname) diff --git a/spyder/utils/tests/test_sourcecode.py b/spyder/utils/tests/test_sourcecode.py index fccfd8142f9..2ab19659872 100644 --- a/spyder/utils/tests/test_sourcecode.py +++ b/spyder/utils/tests/test_sourcecode.py @@ -84,7 +84,7 @@ def test_shortest_path(): shortest_path = os.path.join(*['c:','','documents','test','test.py']) assert sourcecode.shortest_path(files_path_list) == shortest_path -def test_get_file_title(): +def test_disambiguate_fname(): files_path_list = [] if sys.platform.startswith('linux'): fname0 = os.path.join(*['','','documents','test','test.py']) @@ -98,8 +98,8 @@ def test_get_file_title(): files_path_list.append(fname1) title0 = 'test.py - ' + os.path.join(*['test']) title1 = 'test.py - ' + os.path.join(*['projects','test']) - assert sourcecode.get_file_title(files_path_list, fname0) == title0 - assert sourcecode.get_file_title(files_path_list, fname1) == title1 + assert sourcecode.disambiguate_fname(files_path_list, fname0) == title0 + assert sourcecode.disambiguate_fname(files_path_list, fname1) == title1 if __name__ == '__main__': pytest.main() diff --git a/spyder/widgets/editor.py b/spyder/widgets/editor.py index 8c5e35601a6..5e028c2c483 100644 --- a/spyder/widgets/editor.py +++ b/spyder/widgets/editor.py @@ -1155,7 +1155,7 @@ def get_tab_text(self, index, is_modified=None, is_readonly=None): """Return tab title.""" files_path_list = [finfo.filename for finfo in self.data] fname = self.data[index].filename - fname = sourcecode.get_file_title(files_path_list, fname) + fname = sourcecode.disambiguate_fname(files_path_list, fname) return self.__modified_readonly_title(fname, is_modified, is_readonly)