dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

file_manager.py (13218B)


      1 from talon import app, Module, Context, actions, ui, imgui, settings, app, registry
      2 from os.path import expanduser
      3 from subprocess import Popen
      4 from pathlib import Path
      5 from typing import List, Union
      6 import os
      7 import math
      8 import re
      9 from itertools import islice
     10 
     11 mod = Module()
     12 ctx = Context()
     13 
     14 mod.tag("file_manager", desc="Tag for enabling generic file management commands")
     15 mod.list("file_manager_directories", desc="List of subdirectories for the current path")
     16 mod.list("file_manager_files", desc="List of files at the root of the current path")
     17 
     18 
     19 setting_auto_show_pickers = mod.setting(
     20     "file_manager_auto_show_pickers",
     21     type=int,
     22     default=0,
     23     desc="Enable to show the file/directories pickers automatically",
     24 )
     25 setting_folder_limit = mod.setting(
     26     "file_manager_folder_limit",
     27     type=int,
     28     default=1000,
     29     desc="Maximum number of files/folders to iterate",
     30 )
     31 setting_file_limit = mod.setting(
     32     "file_manager_file_limit",
     33     type=int,
     34     default=1000,
     35     desc="Maximum number of files to iterate",
     36 )
     37 setting_imgui_limit = mod.setting(
     38     "file_manager_imgui_limit",
     39     type=int,
     40     default=20,
     41     desc="Maximum number of files/folders to display in the imgui",
     42 )
     43 setting_imgui_string_limit = mod.setting(
     44     "file_manager_string_limit",
     45     type=int,
     46     default=20,
     47     desc="Maximum like of string to display in the imgui",
     48 )
     49 cached_path = None
     50 file_selections = folder_selections = []
     51 current_file_page = current_folder_page = 1
     52 
     53 ctx.lists["self.file_manager_directories"] = []
     54 ctx.lists["self.file_manager_files"] = []
     55 
     56 directories_to_remap = {}
     57 user_path = os.path.expanduser("~")
     58 if app.platform == "windows":
     59     is_windows = True
     60     import ctypes
     61 
     62     GetUserNameEx = ctypes.windll.secur32.GetUserNameExW
     63     NameDisplay = 3
     64 
     65     size = ctypes.pointer(ctypes.c_ulong(0))
     66     GetUserNameEx(NameDisplay, None, size)
     67 
     68     nameBuffer = ctypes.create_unicode_buffer(size.contents.value)
     69     GetUserNameEx(NameDisplay, nameBuffer, size)
     70     one_drive_path = os.path.expanduser(os.path.join("~", "OneDrive"))
     71 
     72     # this is probably not the correct way to check for onedrive, quick and dirty
     73     if os.path.isdir(os.path.expanduser(os.path.join("~", r"OneDrive\Desktop"))):
     74         default_folder = os.path.join("~", "Desktop")
     75 
     76         directories_to_remap = {
     77             "Desktop": os.path.join(one_drive_path, "Desktop"),
     78             "Documents": os.path.join(one_drive_path, "Documents"),
     79             "Downloads": os.path.join(user_path, "Downloads"),
     80             "Music": os.path.join(user_path, "Music"),
     81             "OneDrive": one_drive_path,
     82             "Pictures": os.path.join(one_drive_path, "Pictures"),
     83             "Videos": os.path.join(user_path, "Videos"),
     84         }
     85     else:
     86         # todo use expanduser for cross platform support
     87         directories_to_remap = {
     88             "Desktop": os.path.join(user_path, "Desktop"),
     89             "Documents": os.path.join(user_path, "Documents"),
     90             "Downloads": os.path.join(user_path, "Downloads"),
     91             "Music": os.path.join(user_path, "Music"),
     92             "OneDrive": one_drive_path,
     93             "Pictures": os.path.join(user_path, "Pictures"),
     94             "Videos": os.path.join(user_path, "Videos"),
     95         }
     96 
     97 
     98 @mod.action_class
     99 class Actions:
    100     def file_manager_current_path() -> str:
    101         """Returns the current path for the active file manager."""
    102         return ""
    103 
    104     def file_manager_open_parent():
    105         """file_manager_open_parent"""
    106         return
    107 
    108     def file_manager_go_forward():
    109         """file_manager_go_forward_directory"""
    110 
    111     def file_manager_go_back():
    112         """file_manager_go_forward_directory"""
    113 
    114     def file_manager_open_volume(volume: str):
    115         """file_manager_open_volume"""
    116 
    117     def file_manager_open_directory(path: str):
    118         """opens the directory that's already visible in the view"""
    119 
    120     def file_manager_select_directory(path: str):
    121         """selects the directory"""
    122 
    123     def file_manager_new_folder(name: str):
    124         """Creates a new folder in a gui filemanager or inserts the command to do so for terminals"""
    125 
    126     def file_manager_show_properties():
    127         """Shows the properties for the file"""
    128 
    129     def file_manager_terminal_here():
    130         """Opens terminal at current location"""
    131 
    132     def file_manager_open_file(path: str):
    133         """opens the file"""
    134 
    135     def file_manager_select_file(path: str):
    136         """selects the file"""
    137 
    138     def file_manager_refresh_title():
    139         """Refreshes the title to match current directory. this is for e.g. windows command prompt that will need to do some magic. """
    140         return
    141 
    142     def file_manager_update_lists():
    143         """Forces an update of the lists (e.g., when file or folder created)"""
    144         update_lists()
    145 
    146     def file_manager_toggle_pickers():
    147         """Shows the pickers"""
    148         if gui_files.showing:
    149             gui_files.hide()
    150             gui_folders.hide()
    151         else:
    152             gui_files.show()
    153             gui_folders.show()
    154 
    155     def file_manager_hide_pickers():
    156         """Hides the pickers"""
    157         if gui_files.showing:
    158             gui_files.hide()
    159             gui_folders.hide()
    160 
    161     def file_manager_open_user_directory(path: str):
    162         """expands and opens the user directory"""
    163         # this functionality exists mostly for windows.
    164         # since OneDrive does strange stuff...
    165         if path in directories_to_remap:
    166             path = directories_to_remap[path]
    167 
    168         path = os.path.expanduser(os.path.join("~", path))
    169         actions.user.file_manager_open_directory(path)
    170 
    171     def file_manager_get_directory_by_index(index: int) -> str:
    172         """Returns the requested directory for the imgui display by index"""
    173         index = (current_folder_page - 1) * setting_imgui_limit.get() + index
    174         assert index < len(folder_selections)
    175         return folder_selections[index]
    176 
    177     def file_manager_get_file_by_index(index: int) -> str:
    178         """Returns the requested directory for the imgui display by index"""
    179         index = (current_file_page - 1) * setting_imgui_limit.get() + index
    180         assert index < len(file_selections)
    181         return file_selections[index]
    182 
    183     def file_manager_next_file_page():
    184         """next_file_page"""
    185         global current_file_page
    186         if gui_files.showing:
    187             if current_file_page != total_file_pages:
    188                 current_file_page += 1
    189             else:
    190                 current_file_page = 1
    191             gui_files.show()
    192 
    193     def file_manager_previous_file_page():
    194         """previous_file_page"""
    195         global current_file_page
    196         if gui_files.showing:
    197             if current_file_page != 1:
    198                 current_file_page -= 1
    199             else:
    200                 current_file_page = total_file_pages
    201 
    202             gui_files.show()
    203 
    204     def file_manager_next_folder_page():
    205         """next_folder_page"""
    206         global current_folder_page
    207         if gui_folders.showing:
    208             if current_folder_page != total_folder_pages:
    209                 current_folder_page += 1
    210             else:
    211                 current_folder_page = 1
    212 
    213             gui_folders.show()
    214 
    215     def file_manager_previous_folder_page():
    216         """previous_folder_page"""
    217         global current_folder_page
    218         if gui_folders.showing:
    219             if current_folder_page != 1:
    220                 current_folder_page -= 1
    221             else:
    222                 current_folder_page = total_folder_pages
    223 
    224             gui_folders.show()
    225 
    226 
    227 pattern = re.compile(r"[A-Z][a-z]*|[a-z]+|\d")
    228 
    229 
    230 def create_spoken_forms(symbols, max_len=30):
    231     return [" ".join(list(islice(pattern.findall(s), max_len))) for s in symbols]
    232 
    233 
    234 def is_dir(f):
    235     try:
    236         return f.is_dir()
    237     except:
    238         return False
    239 
    240 
    241 def is_file(f):
    242     try:
    243         return f.is_file()
    244     except:
    245         return False
    246 
    247 
    248 def get_directory_map(current_path):
    249     directories = [
    250         f.name
    251         for f in islice(
    252             current_path.iterdir(), settings.get("user.file_manager_folder_limit", 1000)
    253         )
    254         if is_dir(f)
    255     ]
    256     # print(len(directories))
    257     spoken_forms = create_spoken_forms(directories)
    258     return dict(zip(spoken_forms, directories))
    259 
    260 
    261 def get_file_map(current_path):
    262     files = [
    263         f.name
    264         for f in islice(
    265             current_path.iterdir(), settings.get("user.file_manager_file_limit", 1000)
    266         )
    267         if is_file(f)
    268     ]
    269     # print(str(files))
    270     spoken_forms = create_spoken_forms([p for p in files])
    271     return dict(zip(spoken_forms, [f for f in files]))
    272 
    273 
    274 @imgui.open(y=10, x=900)
    275 def gui_folders(gui: imgui.GUI):
    276     global current_folder_page, total_folder_pages
    277     total_folder_pages = math.ceil(
    278         len(ctx.lists["self.file_manager_directories"]) / setting_imgui_limit.get()
    279     )
    280     gui.text(
    281         "Select a directory ({}/{})".format(current_folder_page, total_folder_pages)
    282     )
    283     gui.line()
    284 
    285     index = 1
    286     current_index = (current_folder_page - 1) * setting_imgui_limit.get()
    287 
    288     while index <= setting_imgui_limit.get() and current_index < len(folder_selections):
    289         name = (
    290             (
    291                 folder_selections[current_index][: setting_imgui_string_limit.get()]
    292                 + ".."
    293             )
    294             if len(folder_selections[current_index]) > setting_imgui_string_limit.get()
    295             else folder_selections[current_index]
    296         )
    297         gui.text("{}: {} ".format(index, name))
    298         current_index += 1
    299         index = index + 1
    300 
    301     # if total_folder_pages > 1:
    302     # gui.spacer()
    303 
    304     # if gui.button('Next...'):
    305     #    actions.user.file_manager_next_folder_page()
    306 
    307     # if gui.button("Previous..."):
    308     #   actions.user.file_manager_previous_folder_page()
    309 
    310 
    311 @imgui.open(y=10, x=1300)
    312 def gui_files(gui: imgui.GUI):
    313     global file_selections, current_file_page, total_file_pages
    314     total_file_pages = math.ceil(len(file_selections) / setting_imgui_limit.get())
    315 
    316     gui.text("Select a file ({}/{})".format(current_file_page, total_file_pages))
    317     gui.line()
    318     index = 1
    319     current_index = (current_file_page - 1) * setting_imgui_limit.get()
    320 
    321     while index <= setting_imgui_limit.get() and current_index < len(file_selections):
    322         name = (
    323             (file_selections[current_index][: setting_imgui_string_limit.get()] + "..")
    324             if len(file_selections[current_index]) > setting_imgui_string_limit.get()
    325             else file_selections[current_index]
    326         )
    327 
    328         gui.text("{}: {} ".format(index, name))
    329         current_index = current_index + 1
    330         index = index + 1
    331 
    332     # if total_file_pages > 1:
    333     #    gui.spacer()
    334 
    335     #    if gui.button('Next...'):
    336     #        actions.user.file_manager_next_file_page()
    337 
    338     #   if gui.button("Previous..."):
    339     #        actions.user.file_manager_previous_file_page()
    340 
    341 
    342 def clear_lists():
    343     global folder_selections, file_selections
    344     if (
    345         len(ctx.lists["self.file_manager_directories"]) > 0
    346         or len(ctx.lists["self.file_manager_files"]) > 0
    347     ):
    348         current_folder_page = current_file_page = 1
    349         ctx.lists["self.file_manager_directories"] = []
    350         ctx.lists["self.file_manager_files"] = []
    351         folder_selections = []
    352         file_selections = []
    353 
    354 
    355 def update_gui():
    356     if gui_folders.showing or setting_auto_show_pickers.get() >= 1:
    357         gui_folders.show()
    358         gui_files.show()
    359 
    360 
    361 def update_lists():
    362     global folder_selections, file_selections, current_folder_page, current_file_page
    363     is_valid_path = False
    364     path = actions.user.file_manager_current_path()
    365     directories = {}
    366     files = {}
    367     folder_selections = []
    368     file_selections = []
    369     # print(path)
    370     try:
    371         current_path = Path(path)
    372         is_valid_path = current_path.is_dir()
    373     except:
    374         is_valid_path = False
    375 
    376     if is_valid_path:
    377         # print("valid..." + str(current_path))
    378         try:
    379             directories = get_directory_map(current_path)
    380             files = get_file_map(current_path)
    381         except:
    382             # print("invalid path...")
    383 
    384             directories = {}
    385             files = {}
    386 
    387     current_folder_page = current_file_page = 1
    388     ctx.lists["self.file_manager_directories"] = directories
    389     ctx.lists["self.file_manager_files"] = files
    390     folder_selections = sorted(directories.values(), key=str.casefold)
    391     file_selections = sorted(files.values(), key=str.casefold)
    392 
    393     update_gui()
    394 
    395 
    396 def win_event_handler(window):
    397     global cached_path
    398 
    399     # on windows, we get events from the clock
    400     # and such, so this check is important
    401     if not window.app.exe or window != ui.active_window():
    402         return
    403 
    404     path = actions.user.file_manager_current_path()
    405 
    406     if not "user.file_manager" in registry.tags:
    407         actions.user.file_manager_hide_pickers()
    408         clear_lists()
    409     elif path:
    410         if cached_path != path:
    411             update_lists()
    412     elif cached_path:
    413         clear_lists()
    414         actions.user.file_manager_hide_pickers()
    415 
    416     cached_path = path
    417 
    418 
    419 def register_events():
    420     ui.register("win_title", win_event_handler)
    421     ui.register("win_focus", win_event_handler)
    422 
    423 
    424 # prevent scary errors in the log by waiting for talon to be fully loaded
    425 # before registering the events
    426 app.register("ready", register_events)
    427