From cec412ce80a1594d2f9268de934d9b7da7f3f59a Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Thu, 28 Jul 2022 23:55:19 +0200 Subject: [PATCH 01/13] goto file and line --- spyder/plugins/workingdirectory/container.py | 68 +++++++++++++++++++- spyder/plugins/workingdirectory/plugin.py | 2 + spyder/widgets/comboboxes.py | 15 ++++- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/spyder/plugins/workingdirectory/container.py b/spyder/plugins/workingdirectory/container.py index 0686cdd67b3..283dc590d54 100644 --- a/spyder/plugins/workingdirectory/container.py +++ b/spyder/plugins/workingdirectory/container.py @@ -12,10 +12,12 @@ import logging import os import os.path as osp +import re # Third party imports from qtpy.compat import getexistingdirectory from qtpy.QtCore import QSize, Signal, Slot +from qtpy.QtWidgets import QComboBox # Local imports from spyder.api.config.decorators import on_conf_change @@ -55,6 +57,9 @@ class WorkingDirectoryToolbar(ApplicationToolbar): class WorkingDirectoryComboBox(PathComboBox): + """Working directory combo box.""" + + edit_goto = Signal(str, int, str) def __init__(self, parent, adjust_to_contents=False, id_=None): super().__init__(parent, adjust_to_contents, id_=id_) @@ -70,8 +75,54 @@ def enterEvent(self, event): """Set current path as the tooltip of the widget on hover.""" self.setToolTip(self.currentText()) + def focusOutEvent(self, event): + """Handle focus out event restoring the last valid selected path.""" + if self.add_current_text_if_valid(): + self.selected() + self.hide_completer() + hide_status = getattr(self.lineEdit(), 'hide_status_icon', None) + if hide_status: + hide_status() + QComboBox.focusOutEvent(self, event) + + # --- Own methods + def valid_text(self): + """Get valid version of current text.""" + directory = self.currentText() + file = None + line_number = None + if directory: + match = re.fullmatch(r"(?:(\d+):)?(.+)", directory[::-1]) + if match: + line_number, directory = match.groups() + if line_number: + line_number = int(line_number[::-1]) + directory = directory[::-1] + directory = osp.abspath(directory) + # It the directory is a file, open containing directory + if os.path.isfile(directory): + file = os.path.basename(directory) + directory = os.path.dirname(directory) + + # If the directory name is malformed, open parent directory + if not os.path.isdir(directory): + directory = os.path.dirname(directory) + if self.is_valid(directory): + return directory, file, line_number + return self.selected_text, file, line_number + + def add_current_text_if_valid(self): + """Add current text to combo box history if valid.""" + directory, file, line_number = self.valid_text() + if file: + self.edit_goto.emit(file, line_number, "") + if directory != self.currentText(): + self.add_text(directory) + if directory: + return True -# ---- Container + +# --- Container # ---------------------------------------------------------------------------- class WorkingDirectoryContainer(PluginMainContainer): """Container for the working directory toolbar.""" @@ -87,6 +138,20 @@ class WorkingDirectoryContainer(PluginMainContainer): The new new working directory path. """ + edit_goto = Signal(str, int, str) + """ + This signal is emitted when a file has been requested. + + Parameters + ---------- + filename: str + The file to open. + line: int + The line to go to. + word: str + The word to go to in the line. + """ + # ---- PluginMainContainer API # ------------------------------------------------------------------------ def setup(self): @@ -111,6 +176,7 @@ def setup(self): # Signals self.pathedit.open_dir.connect(self.chdir) + self.pathedit.edit_goto.connect(self.edit_goto) self.pathedit.activated[str].connect(self.chdir) # Actions diff --git a/spyder/plugins/workingdirectory/plugin.py b/spyder/plugins/workingdirectory/plugin.py index 3b1c6683200..8f948c534f3 100644 --- a/spyder/plugins/workingdirectory/plugin.py +++ b/spyder/plugins/workingdirectory/plugin.py @@ -99,6 +99,8 @@ def on_preferences_available(self): def on_editor_available(self): editor = self.get_plugin(Plugins.Editor) editor.sig_dir_opened.connect(self._editor_change_dir) + container = self.get_container() + container.edit_goto.connect(editor.load) @on_plugin_available(plugin=Plugins.Explorer) def on_explorer_available(self): diff --git a/spyder/widgets/comboboxes.py b/spyder/widgets/comboboxes.py index ba3037d2691..46f2e58d6a3 100644 --- a/spyder/widgets/comboboxes.py +++ b/spyder/widgets/comboboxes.py @@ -261,8 +261,11 @@ def focusOutEvent(self, event): def _complete_options(self): """Find available completion options.""" text = to_text_string(self.currentText()) - opts = glob.glob(text + "*") - opts = sorted([opt for opt in opts if osp.isdir(opt)]) + glob_opts = glob.glob(text + "*") + opts = sorted( + [os.path.normcase(opt) for opt in glob_opts if osp.isdir(opt)]) + opts += sorted( + [os.path.normcase(opt) for opt in glob_opts if opt not in opts]) completer = QCompleter(opts, self) qss = str(APP_STYLESHEET) @@ -276,10 +279,16 @@ def tab_complete(self): If there is a single option available one tab completes the option. """ opts = self._complete_options() + if len(opts) == 0: + return if len(opts) == 1: - self.set_current_text(opts[0] + os.sep) + path = opts[0] + if osp.isdir(path): + path += os.sep + self.set_current_text(path) self.hide_completer() else: + self.set_current_text(os.path.commonprefix(opts)) self.completer().complete() def is_valid(self, qstr=None): From f4cae7d86ce3242ca33aa921e94a52f98cc542e1 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 10:27:05 +0200 Subject: [PATCH 02/13] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- spyder/plugins/workingdirectory/container.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/workingdirectory/container.py b/spyder/plugins/workingdirectory/container.py index d80eb5c1133..3f3bcd9234b 100644 --- a/spyder/plugins/workingdirectory/container.py +++ b/spyder/plugins/workingdirectory/container.py @@ -90,7 +90,7 @@ def focusOutEvent(self, event): hide_status = getattr(self.lineEdit(), 'hide_status_icon', None) if hide_status: hide_status() - QComboBox.focusOutEvent(self, event) + super().focusOutEvent(self, event) # --- Own methods def valid_text(self): @@ -98,6 +98,7 @@ def valid_text(self): directory = self.currentText() file = None line_number = None + if directory: match = re.fullmatch(r"(?:(\d+):)?(.+)", directory[::-1]) if match: @@ -105,8 +106,10 @@ def valid_text(self): if line_number: line_number = int(line_number[::-1]) directory = directory[::-1] + directory = osp.abspath(directory) - # It the directory is a file, open containing directory + + # If the directory is actually a file, open containing directory if os.path.isfile(directory): file = os.path.basename(directory) directory = os.path.dirname(directory) @@ -114,8 +117,10 @@ def valid_text(self): # If the directory name is malformed, open parent directory if not os.path.isdir(directory): directory = os.path.dirname(directory) + if self.is_valid(directory): return directory, file, line_number + return self.selected_text, file, line_number def add_current_text_if_valid(self): @@ -129,7 +134,8 @@ def add_current_text_if_valid(self): return True -# --- Container +# ---- Container +# ---------------------------------------------------------------------------- class WorkingDirectorySpacer(QWidget): ID = 'working_directory_spacer' From d77deb46123321c50d52a7e5efd29f7ea0612c70 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 10:29:14 +0200 Subject: [PATCH 03/13] add comment --- spyder/plugins/workingdirectory/container.py | 1 + 1 file changed, 1 insertion(+) diff --git a/spyder/plugins/workingdirectory/container.py b/spyder/plugins/workingdirectory/container.py index 3f3bcd9234b..73b2c2910e9 100644 --- a/spyder/plugins/workingdirectory/container.py +++ b/spyder/plugins/workingdirectory/container.py @@ -100,6 +100,7 @@ def valid_text(self): line_number = None if directory: + # Search for path/to/file.py:10 where 10 is the line number match = re.fullmatch(r"(?:(\d+):)?(.+)", directory[::-1]) if match: line_number, directory = match.groups() From 3b135146e6ae87bc4090ea420229a213b76241bb Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 10:35:18 +0200 Subject: [PATCH 04/13] remove file completion --- spyder/widgets/comboboxes.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/spyder/widgets/comboboxes.py b/spyder/widgets/comboboxes.py index 841042c7629..0f292c220c6 100644 --- a/spyder/widgets/comboboxes.py +++ b/spyder/widgets/comboboxes.py @@ -309,11 +309,8 @@ def focusOutEvent(self, event): def _complete_options(self): """Find available completion options.""" text = to_text_string(self.currentText()) - glob_opts = glob.glob(text + "*") - opts = sorted( - [os.path.normcase(opt) for opt in glob_opts if osp.isdir(opt)]) - opts += sorted( - [os.path.normcase(opt) for opt in glob_opts if opt not in opts]) + opts = glob.glob(text + "*") + opts = sorted([opt for opt in opts if osp.isdir(opt)]) completer = QCompleter(opts, self) qss = str(APP_STYLESHEET) @@ -330,10 +327,7 @@ def tab_complete(self): if len(opts) == 0: return if len(opts) == 1: - path = opts[0] - if osp.isdir(path): - path += os.sep - self.set_current_text(path) + self.set_current_text(opts[0] + os.sep) self.hide_completer() else: self.set_current_text(os.path.commonprefix(opts)) From 1407e05cbdbe298485028c565c5ff32d2ae9c95a Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 10:37:51 +0200 Subject: [PATCH 05/13] remove unused import --- spyder/plugins/workingdirectory/container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/workingdirectory/container.py b/spyder/plugins/workingdirectory/container.py index b8b6d43a17f..9ee9b270c55 100644 --- a/spyder/plugins/workingdirectory/container.py +++ b/spyder/plugins/workingdirectory/container.py @@ -17,7 +17,7 @@ # Third party imports from qtpy.compat import getexistingdirectory from qtpy.QtCore import QSize, Signal, Slot -from qtpy.QtWidgets import QSizePolicy, QWidget, QComboBox +from qtpy.QtWidgets import QSizePolicy, QWidget # Local imports from spyder.api.config.decorators import on_conf_change From 8971ecbff85ff09684c7c7ed191056ee262a122d Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 11:31:21 +0200 Subject: [PATCH 06/13] fix focus out event --- spyder/plugins/workingdirectory/container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/workingdirectory/container.py b/spyder/plugins/workingdirectory/container.py index 9ee9b270c55..4940bc8ae39 100644 --- a/spyder/plugins/workingdirectory/container.py +++ b/spyder/plugins/workingdirectory/container.py @@ -89,7 +89,7 @@ def focusOutEvent(self, event): hide_status = getattr(self.lineEdit(), 'hide_status_icon', None) if hide_status: hide_status() - super().focusOutEvent(self, event) + super().focusOutEvent(event) # --- Own methods def valid_text(self): From d02c8aeeddbd020ca9d8079bb0315be7315cfef6 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 07:39:45 +0200 Subject: [PATCH 07/13] fix manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 4b3fb2dce0e..1906ded6433 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ include MANIFEST.in include README.md include LICENSE.txt include changelogs/Spyder-5.md +include changelogs/Spyder-6.md include AUTHORS.txt include NOTICE.txt include bootstrap.py From 54ffcbfe6e6c9cf281e30b934ae0648a9e24a329 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Sat, 17 Jun 2023 13:11:10 -0700 Subject: [PATCH 08/13] Fix issues for all-user install. Quote variable interpolation in conditional statements and check for file not just exists. Remove aliases before environment in uninstall. Set executable permission for user only for uninstall-spyder.sh. Only add aliases if shell_init is not null. Use relative path in shortcut command for macOS to allow Spyder.app to be moved. --- installers-conda/resources/post-install.sh | 22 +++++++++++---------- installers-conda/resources/spyder-menu.json | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/installers-conda/resources/post-install.sh b/installers-conda/resources/post-install.sh index 6fb3fcf063d..94385ad9bfc 100755 --- a/installers-conda/resources/post-install.sh +++ b/installers-conda/resources/post-install.sh @@ -33,7 +33,7 @@ m1="# >>> Added by Spyder >>>" m2="# <<< Added by Spyder <<<" add_alias() ( - if [[ ! -e $shell_init || ! -s $shell_init ]]; then + if [[ ! -f "$shell_init" || ! -s "$shell_init" ]]; then echo -e "$m1\n$1\n$m2" > $shell_init exit 0 fi @@ -84,24 +84,26 @@ if [[ \$OSTYPE = "darwin"* ]]; then osascript -e 'quit app "Spyder.app"' 2> /dev/null fi +# Remove aliases from shell startup +if [[ -f "$shell_init" ]]; then + echo "Removing shell commands..." + sed ${sed_opts[@]} "/$m1/,/$m2/d" $shell_init +fi + # Remove shortcut and environment echo "Removing Spyder and environment..." rm -rf ${shortcut_path} rm -rf ${PREFIX} -# Remove aliases from shell startup -if [[ -e ${shell_init} ]]; then - echo "Removing shell commands..." - sed ${sed_opts[@]} "/$m1/,/$m2/d" ${shell_init} -fi - echo "Spyder successfully uninstalled." EOF -chmod +x ${u_spy_exe} +chmod u+x ${u_spy_exe} # ---- -echo "Creating aliases in $shell_init ..." -add_alias "${alias_text}" +if [[ -n "$shell_init" ]]; then + echo "Creating aliases in $shell_init ..." + add_alias "$alias_text" +fi # ---- if [[ $OSTYPE = "linux"* ]]; then diff --git a/installers-conda/resources/spyder-menu.json b/installers-conda/resources/spyder-menu.json index ec884e8de87..41c63a328cb 100644 --- a/installers-conda/resources/spyder-menu.json +++ b/installers-conda/resources/spyder-menu.json @@ -27,7 +27,7 @@ "osx": { "precommand": "eval \"$($SHELL -l -c \"declare -x\")\"", "command": [ - "{{ MENU_ITEM_LOCATION }}/Contents/MacOS/python", + "$(dirname $0)/python", "{{ PREFIX }}/bin/spyder", "$@" ], From f54b1520c13ca44395c5219451488e93caf2a601 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Wed, 14 Jun 2023 13:20:43 -0700 Subject: [PATCH 09/13] Update installer workflow to use superseding setup-micromamba --- .github/workflows/installers-conda.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index 3b0aa2d9bda..d8c7fbeed84 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -82,10 +82,12 @@ jobs: fetch-depth: 0 - name: Setup Build Environment - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: installers-conda/build-environment.yml - extra-specs: python=${{ matrix.python-version }} + create-args: python=${{ matrix.python-version }} + cache-downloads: true + cache-environment: true - name: Build Conda Packages run: python build_conda_pkgs.py --build $pkg @@ -163,12 +165,12 @@ jobs: fetch-depth: 0 - name: Setup Build Environment - uses: mamba-org/provision-with-micromamba@main + uses: mamba-org/setup-micromamba@v1 with: environment-file: installers-conda/build-environment.yml - extra-specs: python=${{ matrix.python-version }} + create-args: python=${{ matrix.python-version }} cache-downloads: true - cache-env: true + cache-environment: true - name: Env Variables run: | From 2fab15177a14e403477779c748c066b5e70f237d Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 19:12:49 +0200 Subject: [PATCH 10/13] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- MANIFEST.in | 1 - spyder/plugins/workingdirectory/container.py | 12 ++++++------ spyder/widgets/comboboxes.py | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 1906ded6433..5ac834fe448 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,7 +5,6 @@ include img_src/oxygen_icon_set/* include MANIFEST.in include README.md include LICENSE.txt -include changelogs/Spyder-5.md include changelogs/Spyder-6.md include AUTHORS.txt include NOTICE.txt diff --git a/spyder/plugins/workingdirectory/container.py b/spyder/plugins/workingdirectory/container.py index 4940bc8ae39..f0398dd2b3b 100644 --- a/spyder/plugins/workingdirectory/container.py +++ b/spyder/plugins/workingdirectory/container.py @@ -91,7 +91,7 @@ def focusOutEvent(self, event): hide_status() super().focusOutEvent(event) - # --- Own methods + # ---- Own methods def valid_text(self): """Get valid version of current text.""" directory = self.currentText() @@ -110,13 +110,13 @@ def valid_text(self): directory = osp.abspath(directory) # If the directory is actually a file, open containing directory - if os.path.isfile(directory): - file = os.path.basename(directory) - directory = os.path.dirname(directory) + if osp.isfile(directory): + file = osp.basename(directory) + directory = osp.dirname(directory) # If the directory name is malformed, open parent directory - if not os.path.isdir(directory): - directory = os.path.dirname(directory) + if not osp.isdir(directory): + directory = osp.dirname(directory) if self.is_valid(directory): return directory, file, line_number diff --git a/spyder/widgets/comboboxes.py b/spyder/widgets/comboboxes.py index 0f292c220c6..73cc6337412 100644 --- a/spyder/widgets/comboboxes.py +++ b/spyder/widgets/comboboxes.py @@ -330,7 +330,7 @@ def tab_complete(self): self.set_current_text(opts[0] + os.sep) self.hide_completer() else: - self.set_current_text(os.path.commonprefix(opts)) + self.set_current_text(osp.commonprefix(opts)) self.completer().complete() def is_valid(self, qstr=None): From 049f77a8c51e6ed993e5f4ca0177a7d7f003e1f6 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 21 Jun 2023 21:42:06 +0200 Subject: [PATCH 11/13] add test --- MANIFEST.in | 1 + .../tests/test_workingdirectory.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 5ac834fe448..1906ded6433 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ include img_src/oxygen_icon_set/* include MANIFEST.in include README.md include LICENSE.txt +include changelogs/Spyder-5.md include changelogs/Spyder-6.md include AUTHORS.txt include NOTICE.txt diff --git a/spyder/plugins/workingdirectory/tests/test_workingdirectory.py b/spyder/plugins/workingdirectory/tests/test_workingdirectory.py index 607a3312b85..08c3d4d7ea8 100644 --- a/spyder/plugins/workingdirectory/tests/test_workingdirectory.py +++ b/spyder/plugins/workingdirectory/tests/test_workingdirectory.py @@ -6,6 +6,7 @@ """Tests for workingdirectory plugin.""" # Standard library imports +import os import os.path as osp import sys from unittest.mock import Mock @@ -13,6 +14,7 @@ # Third-party imports import pytest from qtpy.QtWidgets import QMainWindow +from qtpy.QtCore import Qt # Local imports from spyder.app.cli_options import get_options @@ -116,5 +118,34 @@ def test_get_workingdir_cli(setup_workingdirectory): CONF.reset_to_defaults() +def test_file_goto(qtbot, setup_workingdirectory): + """ + Test that putting a file in the workingdirectory emits a edit_goto signal. + """ + container = setup_workingdirectory.get_container() + + signal_res = {} + + def test_slot(filename, line, word): + signal_res["filename"] = filename + signal_res["line"] = line + + container.edit_goto.connect(test_slot) + + pathedit = container.pathedit + wd = setup_workingdirectory.get_workdir() + filename = osp.join(wd, "myfile_workingdirectory_test.py") + with open(filename, "w") as f: + f.write("\n" * 5) + with qtbot.waitSignal(container.edit_goto): + pathedit.add_text(filename + ":1") + qtbot.keyClick(pathedit, Qt.Key_Return) + + assert signal_res["filename"] in filename + assert signal_res["line"] == 1 + + os.remove(filename) + + if __name__ == "__main__": pytest.main() From 7320930479b4fc6ef6d9156a7680aa0062053642 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 21 Jun 2023 20:22:53 -0500 Subject: [PATCH 12/13] Remove Spyder 5 changelog from tarball --- MANIFEST.in | 1 - setup.cfg | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 1906ded6433..5ac834fe448 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,7 +5,6 @@ include img_src/oxygen_icon_set/* include MANIFEST.in include README.md include LICENSE.txt -include changelogs/Spyder-5.md include changelogs/Spyder-6.md include AUTHORS.txt include NOTICE.txt diff --git a/setup.cfg b/setup.cfg index 753120781e9..02cbc072653 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ ignore = conftest.py crowdin.yml external-deps/** + changelogs/Spyder-5.md changelogs/Spyder-4.md changelogs/Spyder-3.md changelogs/Spyder-2.md From 87a53268b435b4f5436bb1bf7199082590f99c8e Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 21 Jun 2023 21:56:44 -0500 Subject: [PATCH 13/13] Release: Add instruction on how to create changelog for major versions [ci skip] --- RELEASE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index cde2ed3a364..5084cbd7c9a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -161,7 +161,10 @@ To release a new version of Spyder you need to follow these steps: * git pull or git fetch/merge -* Update `changelogs/Spyder-5.md` with `loghub spyder-ide/spyder -m vX.X.X` +* Update `changelogs/Spyder-6.md` with `loghub spyder-ide/spyder -m vX.X.X` + + - When releasing the first alpha of a new major version (e.g. Spyder 7), you need to add a new file called `changelogs/Spyder-7.md` to the tree. + - After that, add `changelogs/Spyder-7.md` to `MANIFEST.in`, remove `changelogs/Spyder-6.md` from it and add that path to the `check-manifest/ignore` section of `setup.cfg`. * Add sections for `New features`, `Important fixes` and `New API features` in `changelogs/Spyder-5.md`. For this take a look at closed issues and PRs for the current milestone.