diff --git a/plugin/core/test_paths.py b/plugin/core/test_paths.py new file mode 100644 index 000000000..6db6fffdc --- /dev/null +++ b/plugin/core/test_paths.py @@ -0,0 +1,93 @@ +import unittest +import random +import string +from .test_windows import MockWindow, MockView +from .workspace import get_project_path + +try: + from typing import List, Optional, Any, Iterable + assert List and Optional and Any and Iterable +except ImportError: + pass + + +def random_file_string(): + return ''.join( + random.choice( + string.ascii_lowercase + string.digits + string.whitespace + "." + ) + for _ in range(random.randint(3, 24)) + ) + + +def mock_file_group(root_dir: str, file_count: int): + out = [] + for i in range(file_count): + out.append(MockView(root_dir + "/" + random_file_string())) + if random.random() > 0.9: + subcontents = mock_file_group( + root_dir + "/" + random_file_string(), + random.randint(1, file_count) + ) + random.shuffle(subcontents) + out.extend(subcontents) + return out + + +class GetProjectPathTests(unittest.TestCase): + + def test_unrelated_files_1(self): + window = MockWindow([ + [ + MockView("/etc/some_configuration_file"), + ], + mock_file_group("/home/user/project_a", 10), + mock_file_group("/home/user_project_b", 10) + ]) + + window.set_folders(["/home/user/project_a", "/home/user/project_b"]) + self.assertEqual(get_project_path(window), None) + + def test_unrelated_files_2(self): + window = MockWindow([ + mock_file_group("/home/user/project_a", 10), + mock_file_group("/home/user_project_b", 10), + [ + MockView("/etc/some_configuration_file"), + ] + ]) + + window.set_folders(["/home/user/project_a", "/home/user/project_b"]) + self.assertEqual(get_project_path(window), "/home/user/project_a") + + def test_single_project(self): + window = MockWindow([ + mock_file_group("/home/user/project_a", 10) + ]) + + window.set_folders(["/home/user/project_a"]) + self.assertEqual(get_project_path(window), "/home/user/project_a") + + def test_double_project(self): + window = MockWindow([ + mock_file_group("/home/user/project_a", 10), + mock_file_group("/home/user/project_b", 10) + ]) + + window.set_folders(["/home/user/project_a", "/home/user/project_b"]) + self.assertEqual(get_project_path(window), "/home/user/project_a") + + def test_triple_project(self): + window = MockWindow([ + mock_file_group("/home/user/project_a", 10), + mock_file_group("/home/user/project_b", 10) + ]) + + window.set_folders(["/home/user/project_a", "/home/user/project_b"]) + self.assertEqual(get_project_path(window), "/home/user/project_a") + + def test_no_project(self): + window = MockWindow([[MockView("/just/pick/the/current/directory.txt")]]) + window.set_folders([]) + + self.assertEqual(get_project_path(window), "/just/pick/the/current") diff --git a/plugin/core/workspace.py b/plugin/core/workspace.py index 9c202d578..0844e8ca6 100644 --- a/plugin/core/workspace.py +++ b/plugin/core/workspace.py @@ -1,37 +1,69 @@ import os try: - from typing import List, Optional, Any - assert List and Optional and Any + from typing import List, Optional, Any, Iterable + assert List and Optional and Any and Iterable except ImportError: pass from .logging import debug -# from .types import WindowLike +from .types import ViewLike + + +def get_filename_from_view(view: ViewLike) -> 'Optional[str]': + if not view: + debug("No view is active in current window") + return None # https://github.com/tomv564/LSP/issues/219 + filename = view.file_name() + if not filename: + debug("Couldn't determine project directory since no folders are open", + "and the current file isn't saved on the disk.") + return filename + + +def get_directory_name(view: ViewLike) -> 'Optional[str]': + filename = get_filename_from_view(view) + if filename: + project_path = os.path.dirname(filename) + return project_path + return None + + +def find_path_among_multi_folders(folders: 'Iterable[str]', + view: ViewLike) -> 'Optional[str]': + filename = get_filename_from_view(view) + if not filename: + return None + folders = [os.path.realpath(f) for f in folders] + file = view.file_name() + if not file: + return None + file = os.path.realpath(file) + while file not in folders: + file = os.path.dirname(file) + if os.path.dirname(file) == file: + # We're at the root of the filesystem. + file = None + break + debug('project path is', file) + return file def get_project_path(window: 'Any') -> 'Optional[str]': """ - Returns the first project folder or the parent folder of the active view + Returns the project folder or the parent folder of the active view """ - if len(window.folders()): + if not window: + return None + num_folders = len(window.folders()) + if num_folders == 0: + return get_directory_name(window.active_view()) + elif num_folders == 1: folder_paths = window.folders() return folder_paths[0] - else: - view = window.active_view() - if view: - filename = view.file_name() - if filename: - project_path = os.path.dirname(filename) - debug("Couldn't determine project directory since no folders are open!", - "Using", project_path, "as a fallback.") - return project_path - else: - debug("Couldn't determine project directory since no folders are open", - "and the current file isn't saved on the disk.") - return None - else: - debug("No view is active in current window") - return None # https://github.com/tomv564/LSP/issues/219 + else: # num_folders > 1 + return find_path_among_multi_folders( + window.folders(), + window.active_view()) def get_common_parent(paths: 'List[str]') -> str: