2021-01-30 23:28:09 +00:00
import os . path
import subprocess
import sys
2021-02-01 23:20:46 +00:00
import mmap , re
2021-01-30 23:28:09 +00:00
import PySimpleGUI as sg
"""
PySimpleGUI Code Finder & Launcher
Use to filter and search your source code tree .
Then run or edit your files
Filter the list of :
* Search using filename
* Searching within the demo program ' s source code (like grep)
The basic file operations are
* Edit a file in your editor
* Run a file
* Filter file list
* Search in files
Additional operations
* Edit this file in editor
2021-02-01 23:20:46 +00:00
Keeps a " history " of the previously chosen folders to easy switching between projects
2021-01-30 23:28:09 +00:00
Copyright 2021 PySimpleGUI . org
"""
2021-02-02 16:29:26 +00:00
def get_file_list_dict ( ) :
2021-01-30 23:28:09 +00:00
"""
2021-02-02 16:29:26 +00:00
Returns dictionary of files
Key is short filename
Value is the full filename and path
2021-01-30 23:28:09 +00:00
2021-02-02 16:29:26 +00:00
: return : Dictionary of demo files
: rtype : Dict [ str : str ]
2021-01-30 23:28:09 +00:00
"""
demo_path = get_demo_path ( )
demo_files_dict = { }
for dirname , dirnames , filenames in os . walk ( demo_path ) :
for filename in filenames :
2021-02-02 16:29:26 +00:00
if filename . endswith ( ' .py ' ) or filename . endswith ( ' .pyw ' ) :
2021-01-30 23:28:09 +00:00
fname_full = os . path . join ( dirname , filename )
if filename not in demo_files_dict . keys ( ) :
demo_files_dict [ filename ] = fname_full
else :
# print(f'duplicate filename found {filename} path {dirname}')
demo_files_dict [ f ' { filename } _1 ' ] = fname_full
return demo_files_dict
2021-02-02 16:29:26 +00:00
def get_file_list ( ) :
"""
Returns list of filenames of files to display
No path is shown , only the short filename
: return : List of filenames
: rtype : List [ str ]
"""
return list ( get_file_list_dict ( ) . keys ( ) )
2021-01-30 23:28:09 +00:00
def get_demo_path ( ) :
2021-02-02 16:29:26 +00:00
"""
Get the top - level folder path
: return : Path to list of files using the user settings for this file . Returns folder of this file if not found
: rtype : str
"""
2021-01-30 23:28:09 +00:00
demo_path = sg . user_settings_get_entry ( ' -demos folder- ' , os . path . dirname ( __file__ ) )
return demo_path
2021-02-02 16:29:26 +00:00
2021-01-30 23:28:09 +00:00
def get_editor ( ) :
2021-02-02 16:29:26 +00:00
"""
Get the path to the editor based on user settings or on PySimpleGUI ' s global settings
: return : Path to the editor
: rtype : str
"""
2021-01-30 23:28:09 +00:00
try : # in case running with old version of PySimpleGUI that doesn't have a global PSG settings path
global_editor = sg . pysimplegui_user_settings . get ( ' -editor program- ' )
except :
global_editor = ' '
return sg . user_settings_get_entry ( ' -editor program- ' , global_editor )
def get_theme ( ) :
2021-02-02 16:29:26 +00:00
"""
Get the theme to use for the program
Value is in this program ' s user settings. If none set, then use PySimpleGUI ' s global default theme
: return : The theme
: rtype : str
"""
# First get the current global theme for PySimpleGUI to use if none has been set for this program
2021-01-30 23:28:09 +00:00
try :
global_theme = sg . theme_global ( )
except :
2021-02-02 16:29:26 +00:00
global_theme = sg . OFFICIAL_PYSIMPLEGUI_THEME
# Get theme from user settings for this program. Use global theme if no entry found
2021-01-30 23:28:09 +00:00
return sg . user_settings_get_entry ( ' -theme- ' , global_theme )
2021-02-01 23:20:46 +00:00
2021-01-30 23:28:09 +00:00
def find_in_file ( string ) :
"""
Search through the demo files for a string .
The case of the string and the file contents are ignored
: param string : String to search for
: return : List of files containing the string
: rtype : List [ str ]
"""
2021-02-01 23:20:46 +00:00
# So you face a prediciment here. You wish to read files, both small and large; however the bigger the file/bigger the list, the longer to read the file.
# This probably isn't what you want, right?
# Well, we can't use a direct command line to run grep and parse. But it is an option. The user may not have it.
# We could check if grep exists and if not use our method; but it isn't the best way.
# So using background knowldge, we know that grep is *very* fast.
#
# Why?
# Grep reads a *ton* of files into memory then searches through the memory to find the string or regex/pattern corresponding to the file.
# How can we load a file into memory on python as fast as grep whilst keeping it universal?
# memory mapping (mmap).
# We can't load a lot of files into memory as we may face issues with watchdog on other operating systems. So we load one file at a time and search though there.
# This will allow the fastest searching and loading of a file without sacrificing read times.
# 2.8 seconds on the highend for both small and large files in memory.
# We also don't have to iterate over lines this way.
2021-02-02 16:29:26 +00:00
file_list_dict = get_file_list_dict ( )
2021-01-30 23:28:09 +00:00
file_list = [ ]
2021-02-01 23:20:46 +00:00
2021-02-02 16:29:26 +00:00
for file in file_list_dict :
2021-01-30 23:28:09 +00:00
try :
2021-02-02 16:29:26 +00:00
full_filename = file_list_dict [ file ]
2021-02-01 23:20:46 +00:00
with open ( full_filename , ' rb ' , 0 ) as f , mmap . mmap ( f . fileno ( ) , 0 , access = mmap . ACCESS_READ ) as s :
if re . search ( br ' (?i) ' + bytes ( re . escape ( string . lower ( ) ) , ' utf-8 ' ) , s ) :
# ^^^^^^^^^-----------------------------------> re.escape
# (In case the user types ( or any other character that can be treated like a regex)
file_list . append ( file )
except ValueError :
pass # caused by an empty file so no problem skipping it
2021-01-30 23:28:09 +00:00
except Exception as e :
2021-02-01 23:20:46 +00:00
print ( f ' { file } ' , e )
2021-01-30 23:28:09 +00:00
return list ( set ( file_list ) )
def settings_window ( ) :
"""
Show the settings window .
This is where the folder paths and program paths are set .
Returns True if settings were changed
: return : True if settings were changed
: rtype : ( bool )
"""
editor_program = get_editor ( )
layout = [ [ sg . T ( ' Program Settings ' , font = ' DEFAIULT 18 ' ) ] ,
2021-02-02 16:29:26 +00:00
[ sg . T ( ' Path to Tree ' , size = ( 20 , 1 ) ) ,
2021-02-01 23:20:46 +00:00
sg . Combo ( sg . user_settings_get_entry ( ' -folder names- ' , [ ] ) , default_value = sg . user_settings_get_entry ( ' -demos folder- ' , ' ' ) , size = ( 50 , 1 ) , key = ' -FOLDERNAME- ' ) ,
sg . FolderBrowse ( ' Folder Browse ' , target = ' -FOLDERNAME- ' ) , sg . B ( ' Clear History ' ) ] ,
2021-01-30 23:28:09 +00:00
[ sg . T ( ' Editor Program ' , size = ( 20 , 1 ) ) , sg . In ( sg . user_settings_get_entry ( ' -editor program- ' , editor_program ) , k = ' -EDITOR PROGRAM- ' ) , sg . FileBrowse ( ) ] ,
[ sg . T ( r " For PyCharm, Add this to your PyCharm main program ' s folder \ bin \ pycharm.bat " ) ] ,
[ sg . Combo ( [ ' ' ] + sg . theme_list ( ) , sg . user_settings_get_entry ( ' -theme- ' , None ) , k = ' -THEME- ' ) ] ,
[ sg . B ( ' Ok ' , bind_return_key = True ) , sg . B ( ' Cancel ' ) ] ,
]
window = sg . Window ( ' Settings ' , layout )
2021-02-01 23:20:46 +00:00
settings_changed = False
while True :
event , values = window . read ( )
if event in ( ' Cancel ' , sg . WIN_CLOSED ) :
break
if event == ' Ok ' :
sg . user_settings_set_entry ( ' -demos folder- ' , values [ ' -FOLDERNAME- ' ] )
new_editor = editor_program if not values [ ' -EDITOR PROGRAM- ' ] else values [ ' -EDITOR PROGRAM- ' ]
sg . user_settings_set_entry ( ' -editor program- ' , new_editor )
new_theme = get_theme ( ) if values [ ' -THEME- ' ] == ' ' else values [ ' -THEME- ' ]
sg . user_settings_set_entry ( ' -theme- ' , new_theme )
sg . user_settings_set_entry ( ' -folder names- ' , list ( set ( sg . user_settings_get_entry ( ' -folder names- ' , [ ] ) + [ values [ ' -FOLDERNAME- ' ] , ] ) ) )
settings_changed = True
break
elif event == ' Clear History ' :
2021-02-02 16:29:26 +00:00
sg . user_settings_set_entry ( ' -folder names- ' , [ ] )
2021-02-01 23:20:46 +00:00
sg . user_settings_set_entry ( ' -last filename- ' , ' ' )
window [ ' -FOLDERNAME- ' ] . update ( values = [ ] , value = ' ' )
window . close ( )
return settings_changed
2021-01-30 23:28:09 +00:00
# --------------------------------- Create the window ---------------------------------
def make_window ( ) :
"""
Creates the main window
: return : The main window object
: rtype : ( Window )
"""
theme = get_theme ( )
if not theme :
theme = sg . OFFICIAL_PYSIMPLEGUI_THEME
sg . theme ( theme )
# First the window layout...2 columns
find_tooltip = " Find in file \n Enter a string in box to search for string inside of the files. \n File list will update with list of files string found inside. "
filter_tooltip = " Filter files \n Enter a string in box to narrow down the list of files. \n File list will update with list of files with string in filename. "
ML_KEY = ' -ML- ' # Multline's key
left_col = [
2021-02-02 16:29:26 +00:00
[ sg . Listbox ( values = get_file_list ( ) , select_mode = sg . SELECT_MODE_EXTENDED , size = ( 50 , 20 ) , key = ' -DEMO LIST- ' ) ] ,
2021-01-30 23:28:09 +00:00
[ sg . Text ( ' Filter: ' , tooltip = filter_tooltip ) , sg . Input ( size = ( 25 , 1 ) , enable_events = True , key = ' -FILTER- ' , tooltip = filter_tooltip ) ,
sg . T ( size = ( 20 , 1 ) , k = ' -FILTER NUMBER- ' ) ] ,
[ sg . Button ( ' Run ' ) , sg . B ( ' Edit ' ) , sg . B ( ' Clear ' ) ] ,
[ sg . Text ( ' Find: ' , tooltip = find_tooltip ) , sg . Input ( size = ( 25 , 1 ) , enable_events = True , key = ' -FIND- ' , tooltip = find_tooltip ) ,
sg . T ( size = ( 20 , 1 ) , k = ' -FIND NUMBER- ' ) ] ]
right_col = [
[ sg . Multiline ( size = ( 70 , 21 ) , write_only = True , key = ML_KEY , reroute_stdout = True , echo_stdout_stderr = True ) ] ,
[ sg . Button ( ' Edit Me (this program) ' ) , sg . B ( ' Settings ' ) , sg . Button ( ' Exit ' ) ] ,
[ sg . T ( ' PySimpleGUI ver ' + sg . version . split ( ' ' ) [ 0 ] + ' tkinter ver ' + sg . tclversion_detailed , font = ' Default 8 ' , pad = ( 0 , 0 ) ) ] ,
]
# ----- Full layout -----
layout = [ [ sg . Text ( ' PySimpleGUI Project File Searcher & Launcher ' , font = ' Any 20 ' ) ] ,
2021-02-01 23:20:46 +00:00
[ sg . T ( ' Click settings to set top of your tree or choose a previously chosen folder ' ) ,
sg . Combo ( sg . user_settings_get_entry ( ' -folder names- ' , [ ] ) , default_value = sg . user_settings_get_entry ( ' -demos folder- ' , ' ' ) , size = ( 50 , 1 ) , key = ' -FOLDERNAME- ' , enable_events = True , readonly = True ) ] ,
2021-01-30 23:28:09 +00:00
sg . vtop ( [ sg . Column ( left_col , element_justification = ' c ' ) , sg . Col ( right_col , element_justification = ' c ' ) ] ) ]
# --------------------------------- Create Window ---------------------------------
2021-02-02 16:29:26 +00:00
window = sg . Window ( ' PSG Finder Launcher ' , layout , finalize = True )
2021-01-30 23:28:09 +00:00
sg . cprint_set_output_destination ( window , ML_KEY )
return window
# --------------------------------- Main Program Layout ---------------------------------
def main ( ) :
"""
The main program that contains the event loop .
It will call the make_window function to create the window .
"""
editor_program = get_editor ( )
2021-02-02 16:29:26 +00:00
file_list_dict = get_file_list_dict ( )
file_list = get_file_list ( )
2021-01-30 23:28:09 +00:00
window = make_window ( )
2021-02-02 16:29:26 +00:00
window [ ' -FILTER NUMBER- ' ] . update ( f ' { len ( file_list ) } files ' )
2021-01-30 23:28:09 +00:00
while True :
event , values = window . read ( )
if event in ( sg . WINDOW_CLOSED , ' Exit ' ) :
break
if event == ' Edit ' :
for file in values [ ' -DEMO LIST- ' ] :
sg . cprint ( f ' Editing using { editor_program } ' , text_color = ' white ' , background_color = ' red ' , end = ' ' )
sg . cprint ( ' ' )
2021-02-02 16:29:26 +00:00
sg . cprint ( f ' { file_list_dict [ file ] } ' , text_color = ' white ' , background_color = ' purple ' )
execute_command_subprocess ( f ' { editor_program } ' , f ' " { file_list_dict [ file ] } " ' )
2021-01-30 23:28:09 +00:00
elif event == ' Run ' :
sg . cprint ( ' Running.... ' , c = ' white on green ' , end = ' ' )
sg . cprint ( ' ' )
for file in values [ ' -DEMO LIST- ' ] :
2021-02-02 16:29:26 +00:00
file_to_run = str ( file_list_dict [ file ] )
2021-01-30 23:28:09 +00:00
sg . cprint ( file_to_run , text_color = ' white ' , background_color = ' purple ' )
2021-01-31 00:09:20 +00:00
execute_command_subprocess ( ' python ' , f ' { file_to_run } ' )
2021-01-30 23:28:09 +00:00
# run_py(file_to_run)
elif event . startswith ( ' Edit Me ' ) :
sg . cprint ( f ' opening using { editor_program } \n This file - { __file__ } ' , text_color = ' white ' , background_color = ' green ' , end = ' ' )
2021-01-31 00:09:20 +00:00
execute_command_subprocess ( f ' { editor_program } ' , f ' " { __file__ } " ' )
2021-01-30 23:28:09 +00:00
elif event == ' -FILTER- ' :
2021-02-02 16:29:26 +00:00
new_list = [ i for i in file_list if values [ ' -FILTER- ' ] . lower ( ) in i . lower ( ) ]
2021-01-30 23:28:09 +00:00
window [ ' -DEMO LIST- ' ] . update ( new_list )
window [ ' -FILTER NUMBER- ' ] . update ( f ' { len ( new_list ) } files ' )
window [ ' -FIND NUMBER- ' ] . update ( ' ' )
window [ ' -FIND- ' ] . update ( ' ' )
elif event == ' -FIND- ' :
file_list = find_in_file ( values [ ' -FIND- ' ] )
window [ ' -DEMO LIST- ' ] . update ( sorted ( file_list ) )
window [ ' -FIND NUMBER- ' ] . update ( f ' { len ( file_list ) } files ' )
window [ ' -FILTER NUMBER- ' ] . update ( ' ' )
window [ ' -FILTER- ' ] . update ( ' ' )
elif event == ' Settings ' :
if settings_window ( ) is True :
window . close ( )
window = make_window ( )
editor_program = get_editor ( )
2021-02-02 16:29:26 +00:00
file_list_dict = get_file_list_dict ( )
file_list = get_file_list ( )
window [ ' -FILTER NUMBER- ' ] . update ( f ' { len ( file_list ) } files ' )
2021-01-30 23:28:09 +00:00
elif event == ' Clear ' :
window [ ' -FILTER- ' ] . update ( ' ' )
2021-02-02 16:29:26 +00:00
window [ ' -FILTER NUMBER- ' ] . update ( f ' { len ( file_list ) } files ' )
2021-01-30 23:28:09 +00:00
window [ ' -FIND- ' ] . update ( ' ' )
2021-02-02 16:29:26 +00:00
window [ ' -DEMO LIST- ' ] . update ( file_list )
2021-01-30 23:28:09 +00:00
window [ ' -FIND NUMBER- ' ] . update ( ' ' )
2021-02-01 23:20:46 +00:00
elif event == ' -FOLDERNAME- ' :
sg . user_settings_set_entry ( ' -demos folder- ' , values [ ' -FOLDERNAME- ' ] )
2021-02-02 16:29:26 +00:00
file_list_dict = get_file_list_dict ( )
file_list = get_file_list ( )
window [ ' -DEMO LIST- ' ] . update ( values = file_list )
window [ ' -FILTER NUMBER- ' ] . update ( f ' { len ( file_list ) } files ' )
2021-01-30 23:28:09 +00:00
window . close ( )
2021-01-31 00:09:20 +00:00
def execute_command_subprocess ( command , * args , wait = False ) :
if sys . platform == ' linux ' :
arg_string = ' '
for arg in args :
arg_string + = ' ' + str ( arg )
sp = subprocess . Popen ( [ ' python3 ' + arg_string , ] , shell = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
else :
expanded_args = ' ' . join ( args )
sp = subprocess . Popen ( [ command , expanded_args ] , shell = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
if wait :
out , err = sp . communicate ( )
if out :
print ( out . decode ( " utf-8 " ) )
if err :
print ( err . decode ( " utf-8 " ) )
2021-01-30 23:28:09 +00:00
if __name__ == ' __main__ ' :
2021-02-01 23:20:46 +00:00
main ( )