diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index c8045c0e8..7c4124da0 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -665,6 +665,8 @@ class FilenamePrompt(_BasePrompt): self._init_texts(question) self._init_key_label() + self._user_expanded = False + self._lineedit = LineEdit(self) if question.default: self._lineedit.setText(question.default) @@ -698,6 +700,11 @@ class FilenamePrompt(_BasePrompt): if os.altsep is not None: separators += os.altsep + # If the path is not the same as the path with expanduser set a boolean + if os.path.expanduser(path) != path: + path = os.path.expanduser(path) + self._user_expanded = True + dirname = os.path.dirname(path) basename = os.path.basename(path) if not tabbed: @@ -746,6 +753,9 @@ class FilenamePrompt(_BasePrompt): # On Windows, when we have C:\foo and tab over .., we get C:\ path = path.rstrip(os.sep) + if self._user_expanded: + path = path.replace(os.path.expanduser('~'), '~', 1) + log.prompt.debug('Inserting path {}'.format(path)) self._lineedit.setText(path) self._lineedit.setFocus() diff --git a/tests/unit/mainwindow/test_prompt.py b/tests/unit/mainwindow/test_prompt.py index 67403101c..352c6fc44 100644 --- a/tests/unit/mainwindow/test_prompt.py +++ b/tests/unit/mainwindow/test_prompt.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import os +import pathlib import pytest from qutebrowser.qt.core import Qt @@ -113,3 +114,56 @@ class TestFileCompletion: """With / as path, show root contents.""" prompt = get_prompt('/') assert prompt._file_model.rootPath() == '/' + + @pytest.fixture + def home_path(self, monkeypatch, tmp_path): + homedir = str(tmp_path) + monkeypatch.setenv('HOME', homedir) # POSIX + monkeypatch.setenv('USERPROFILE', homedir) # Windows + assert str(pathlib.Path.home()) == homedir + return tmp_path + + def test_expand_user(self, qtbot, get_prompt, home_path): + for directory in ['bar', 'bat', 'foo']: + (home_path / directory).mkdir() + + prompt = get_prompt('/') + prompt._lineedit.setText('') + with qtbot.wait_signal(prompt._file_model.directoryLoaded): + # _set_fileview_root isn't run unless there is an actual keypress + qtbot.keyPress(prompt._lineedit, Qt.Key_AsciiTilde) + + assert (pathlib.Path(prompt._file_model.rootPath()) / + prompt._to_complete) == pathlib.Path.home() + # The first character on the lineedit should remain ~ + prompt.item_focus('next') + prompt.item_focus('next') + assert prompt._lineedit.text()[0] == '~' + + def test_expand_once(self, qtbot, get_prompt, home_path): + home_path.joinpath(*home_path.parts[1:]).mkdir(parents=True) + + prompt = get_prompt('/') + prompt._lineedit.setText('~' + str(home_path)) + with qtbot.wait_signal(prompt._file_model.directoryLoaded): + # _set_fileview_root isn't run unless there is an actual keypress + qtbot.keyPress(prompt._lineedit, Qt.Key_Slash) + + # The second instance of home_path shouldn't be replaced with ~ + assert prompt._lineedit.text()[:-1] == '~' + str(home_path) + + def test_dont_contract(self, qtbot, get_prompt, home_path): + for directory in ['bar', 'bat', 'foo']: + (home_path / directory).mkdir() + + prompt = get_prompt('/') + prompt._lineedit.setText(str(home_path)) + with qtbot.wait_signal(prompt._file_model.directoryLoaded): + # _set_fileview_root isn't run unless there is an actual keypress + qtbot.keyPress(prompt._lineedit, Qt.Key_Slash) + + prompt.item_focus('next') + prompt.item_focus('next') + # If $HOME is contracted to `~` this test will fail as that is unexpected + assert (prompt._lineedit.text() != + prompt._lineedit.text().replace(os.path.expanduser('~'), '~', 1))