diff --git a/gitignore_plugin.py b/gitignore_plugin.py index 4273dea..df85eb1 100644 --- a/gitignore_plugin.py +++ b/gitignore_plugin.py @@ -3,29 +3,37 @@ import os import os.path import threading +import platform from time import sleep # Used for output suppression when calling subprocess functions; see # http://stackoverflow.com/questions/10251391/suppressing-output-in-python-subprocess-call devnull = open(os.devnull, 'w') +# Used to prevent a new command prompt window from popping up every time a new +# process is spawned on Windows. See +# https://docs.python.org/2/library/subprocess.html#subprocess.STARTUPINFO +if platform.system() == 'Windows': + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE +else: + startupinfo = None + def start(): # Gets invoked at the bottom of this file. """ Regularly (every 5s) updates the file_exclude_patterns setting from a background thread. - """ + """ if is_first_launch(): migrate_exclude_patterns() record_first_launch() def run(): - while True: - update_file_exclude_patterns() - sleep(5) - - thread = threading.Thread(target=run) - thread.daemon = True - thread.start() + update_file_exclude_patterns() + sublime.set_timeout(run, 5000) + + run() def update_file_exclude_patterns(): """ @@ -39,11 +47,22 @@ def update_file_exclude_patterns(): file_exclude_patterns = s.get('extra_file_exclude_patterns', []) folder_exclude_patterns = s.get('extra_folder_exclude_patterns', []) for path in all_ignored_paths(): - if os.path.isdir(path): - folder_exclude_patterns.append(path.rstrip(u'/')) + is_directory = os.path.isdir(path) + if platform.system() == 'Windows': + # For some bizarre reason Sublime wants all its filenames to look like + # C/somedir/somefile + # instead of + # C:\somedir\somefile + # as they are normally written on Windows, and will not understand the + # latter at all. All the other functions in this file return paths with + # OS-standard separtors and include the colon after the drive letter on + # Windows, so we need to convert them here to Sublime-format. + path = windows_path_to_sublime_path(path) + if is_directory: + folder_exclude_patterns.append(path) else: file_exclude_patterns.append(path) - + new_files = set(file_exclude_patterns) old_files = set(s.get('file_exclude_patterns', [])) new_folders = set(folder_exclude_patterns) @@ -52,8 +71,8 @@ def update_file_exclude_patterns(): # Only make changes if anything has actually changed, to avoid spamming the # sublime console if new_files != old_files or new_folders != old_folders: - s.set('file_exclude_patterns', file_exclude_patterns) - s.set('folder_exclude_patterns', folder_exclude_patterns) + s.set('file_exclude_patterns', list(file_exclude_patterns)) + s.set('folder_exclude_patterns', list(folder_exclude_patterns)) sublime.save_settings("Preferences.sublime-settings") def all_ignored_paths(): @@ -91,7 +110,7 @@ def folder_ignored_paths(folder): # find the .git folder of the repo containing it: if is_in_git_repo(folder): repos.add(parent_repo_path(folder)) - + # Now we find all the ignored paths in any of the above repos for git_repo in repos: ignored_paths = repo_ignored_paths(git_repo) @@ -108,7 +127,8 @@ def is_in_git_repo(folder): ["git", "rev-parse", "--is-inside-work-tree"], cwd=folder, stdout=devnull, - stderr=devnull + stderr=devnull, + startupinfo=startupinfo ) return exit_code == 0 @@ -119,11 +139,17 @@ def parent_repo_path(folder): parent repo. """ - return subprocess.Popen( - ['git', 'rev-parse', '--show-toplevel'], - stdout=subprocess.PIPE, - cwd=folder - ).stdout.read().decode('utf-8', 'ignore').strip() + # abspath call converts forward slashes to backslashes on Windows; we do + # this wherever necessary to keep the format of our paths standardised on + # Windows. + return os.path.abspath( + subprocess.Popen( + ['git', 'rev-parse', '--show-toplevel'], + stdout=subprocess.PIPE, + cwd=folder, + startupinfo=startupinfo + ).stdout.read().decode('utf-8', 'ignore').strip() + ) def find_git_repos(folder): """ @@ -145,7 +171,8 @@ def repo_ignored_paths(git_repo): command_output = subprocess.Popen( ['git', 'clean', '-ndX'], stdout=subprocess.PIPE, - cwd=git_repo + cwd=git_repo, + startupinfo=startupinfo ).stdout.read() command_output = command_output.decode('utf-8', 'ignore') @@ -157,8 +184,9 @@ def repo_ignored_paths(git_repo): # Each line in `lines` now looks something like: # "Would remove foo/bar/yourfile.txt" - relative_paths = [line.replace(u'Would remove ', u'', 1) for line in lines] - absolute_paths = [git_repo + u'/' + path for path in relative_paths] + relative_paths = [line.replace(u'Would remove ', u'', 1).rstrip(u'/') + for line in lines] + absolute_paths = [os.path.join(git_repo, path) for path in relative_paths] return absolute_paths @@ -182,5 +210,20 @@ def record_first_launch(): s = sublime.load_settings("gitignorer.sublime-settings") s.set('_sublime_gitignorer_has_run', True) sublime.save_settings("gitignorer.sublime-settings") + +def windows_path_to_sublime_path(path): + """ + Removes the colon after the drive letter and replaces backslashes with + slashes. + + e.g. + + windows_path_to_sublime_path("C:\somedir\somefile") + == "C/somedir/somefile" + """ + + assert(path[1] == u':') + without_colon = path[0] + path[2:] + return without_colon.replace(u'\\', u'/') start() \ No newline at end of file