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: | 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/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. 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", "$@" ], 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 diff --git a/spyder/plugins/workingdirectory/container.py b/spyder/plugins/workingdirectory/container.py index b3aba0206f2..f0398dd2b3b 100644 --- a/spyder/plugins/workingdirectory/container.py +++ b/spyder/plugins/workingdirectory/container.py @@ -12,6 +12,7 @@ import logging import os import os.path as osp +import re # Third party imports from qtpy.compat import getexistingdirectory @@ -57,6 +58,9 @@ class WorkingDirectoryToolbar(ApplicationToolbar): class WorkingDirectoryComboBox(PathComboBox): + """Working directory combo box.""" + + edit_goto = Signal(str, int, str) def __init__(self, parent): super().__init__( @@ -77,7 +81,61 @@ 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() + super().focusOutEvent(event) + + # ---- Own methods + def valid_text(self): + """Get valid version of current text.""" + directory = self.currentText() + file = None + 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() + if line_number: + line_number = int(line_number[::-1]) + directory = directory[::-1] + + directory = osp.abspath(directory) + + # If the directory is actually a file, open containing directory + if osp.isfile(directory): + file = osp.basename(directory) + directory = osp.dirname(directory) + + # If the directory name is malformed, open parent directory + if not osp.isdir(directory): + directory = osp.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 +# ---------------------------------------------------------------------------- class WorkingDirectorySpacer(QWidget): ID = 'working_directory_spacer' @@ -107,6 +165,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): @@ -128,6 +200,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 8a3a990bab7..9e4630bfe77 100644 --- a/spyder/plugins/workingdirectory/plugin.py +++ b/spyder/plugins/workingdirectory/plugin.py @@ -96,6 +96,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/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() diff --git a/spyder/widgets/comboboxes.py b/spyder/widgets/comboboxes.py index 107c7a58bad..73cc6337412 100644 --- a/spyder/widgets/comboboxes.py +++ b/spyder/widgets/comboboxes.py @@ -324,10 +324,13 @@ 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) self.hide_completer() else: + self.set_current_text(osp.commonprefix(opts)) self.completer().complete() def is_valid(self, qstr=None):