Skip to content

Instantly share code, notes, and snippets.

@ssbarnea
Created November 4, 2011 17:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssbarnea/1339907 to your computer and use it in GitHub Desktop.
Save ssbarnea/1339907 to your computer and use it in GitHub Desktop.
Python script that enables and handles edit:// hyperlinks under Windows.
import sys, os, subprocess, shutil
import traceback
from _winreg import *
from ctypes import c_int, WINFUNCTYPE, windll
from ctypes.wintypes import HWND, LPCSTR, UINT
prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)
paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", None), (1, "flags", 0)
MessageBox = prototype(("MessageBoxA", windll.user32), paramflags)
# use flag=1 to make it Yes|No
# See http://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx)
"""
iexplore calls with full URL: edit://some
chrome calls with: //some
"""
# possible locations for source code, it is used for matching the path from inside the URL with the local one
# order is important
roots = [
"C:/Automation/",
"C:/Python_workspace/trunk/",
"C:/AutomationScripts/",
"C:/_autoscripts/",
"C:/dev/auto",
'~/workspace', # ~ is your %HOME% dude ;)
]
def getJetBrainsPath():
"""
Returns the path to the newest executable of the installed version of one of the JetBrains tools (PyCharm or IDEA).
If nothing is found it will return None.
"""
products = ['PyCharm','IntelliJ IDEA'] # made PyCharm first but it should be adter IDEA
exes = ["\\bin\\pycharm.exe", "\\bin\\idea.exe"]
for i in range(len(products)):
try:
key = OpenKey(HKEY_CURRENT_USER, "Software\\JetBrains\\%s" % products[i])
versions = []
try:
j = 0
while True:
versions.append(EnumKey(key,j))
j+=1
except:
pass
versions.sort(reverse=True)
for ver in versions:
key = OpenKey(HKEY_CURRENT_USER, "Software\\JetBrains\\%s\\%s" % (products[i],ver))
path = QueryValue(key, None)
if path:
path = path + exes[i]
if os.path.isfile(path):
return path
except Exception, e:
#print e
#traceback.print_exc()
pass
return None
def getNotepadPlusPlusPath():
try:
key = OpenKey(HKEY_LOCAL_MACHINE, "Software\\Notepad++")
path = QueryValue(key, None)
if path:
path = path + "\\notepad++.exe"
if os.path.isfile(path):
return path
except Exception, e:
print e
traceback.print_exc()
pass
return None
def getEditor():
"""
Returns the full path to a editor. It will pick one of:
* PyCharm
* IntelliJ IDEA
* Eclipse
* Notepad++
* Notepad (bleah!)
"""
# 1: JetBrains tools have priority because they are commercial tools (and better)
jet = getJetBrainsPath()
if jet:
return jet
# 2: Eclipse sucks big time, they don't even have an installer and impossible to properly detect it's location, let's guess
editors = [ # don't play with the order, it will change the app being used to open the files
"C:\\dev\\eclipse\\eclipse.exe",
"${PROGRAMFILES}\\eclipse\\eclipse.exe",
"C:\\eclipse\\eclipse.exe",
"${PROGRAMFILES}\\eclipse37\\eclipse.exe",
]
for i in range(len(editors)):
editor = os.path.abspath(os.path.expanduser(os.path.expandvars(editors[i])))
if os.path.isdir(editor):
return editor
# 3: Notepad++
editor = getNotepadPlusPlusPath()
if editor:
return editor
# 4: fallback, the infamous `notepad`
return 'notepad.exe'
def sanitize(filename):
"""
Use this function to auto-correct bad paths.
For example if you keep the code in another location than the server.
"""
filename = os.path.abspath(filename)
if not os.path.isfile(filename):
for local_root in local_roots:
for root in roots:
if filename.find(root, 0)==0: # if the URL-path starts with our root
tmp = filename.replace(root, local_root)
if os.path.isfile(tmp):
return tmp
return filename
if __name__ == '__main__':
local_roots = []
for i in range(len(roots)):
roots[i] = os.path.abspath(os.path.expanduser(roots[i]))
if os.path.isdir(roots[i]):
local_roots.append(roots[i])
if not local_roots:
raise Exception("Unable to detect any local root directory in `roots` list: \n\n%s" % ("\n".join(roots)))
# print local_root
print getEditor()
cmd = " ".join(sys.argv)
try:
if len(sys.argv)<2:
# Called without parameters, this will trigger the build-in installer
p = subprocess.Popen('where python', stdout=subprocess.PIPE)
python = p.communicate()[0].split()[0] # first line
src = os.path.abspath(sys.argv[0])
dst = os.path.dirname(python) + "\\" + os.path.basename(sys.argv[0])
#print src
#print dst
if src != dst:
# not it's clear that we are supposed to install, but we need admin priviledges for this
import pythoncom
import pywintypes
import win32api
from win32com.shell import shell
result = MessageBox(
text="Do you want me to install edit:// protocol handler script?" \
"\n\nInstall source: %s" \
"\nInstall destination: %s" % (src, dst),
caption=sys.argv[0],
flags = 1)
if result == 1:
try:
shutil.copyfile(src, dst)
# install protocol handler
key = CreateKey(HKEY_CLASSES_ROOT, "edit")
SetValue(key, None, REG_SZ, "URL:edit Protocol")
key = CreateKey(HKEY_CLASSES_ROOT, "edit\\shell\\open\\command")
SetValue(key, None, REG_SZ, 'pythonw "%s" "%%1"' % dst)
key = CreateKey(HKEY_CLASSES_ROOT, "edit\\shell\\edit\\command")
SetValue(key, None, REG_SZ, 'pythonw "%s" "%%1"' % dst)
except e:
if not shell.IsUserAnAdmin():
raise(str(e) + "\n\nOne possible reason could be the lack of priviledges, so please run me as Administrator in order to be able to install.")
sys.exit(0)
else:
MessageBox(text="Called without filename parameter and the script is already installed.\n\nSo there is nothing to do!", caption=sys.argv[0])
sys.exit(0)
filename = sys.argv[1].split("edit://")[1].strip('/')
filename = os.path.abspath(filename)
origFilename = filename
filename = sanitize(filename)
if not os.path.isfile(filename):
MessageBox(text="File not found: %s\n\nOriginal filename: %s" % (filename, origFilename), caption=sys.argv[0])
else:
try:
os.startfile(filename, "edit")
except WindowsError, e:
if e.winerror == 1155: # filetype has no associated 'edit' action.
editor = getEditor()
cmd = '"%s" "%s"' % (editor, filename)
print cmd
print subprocess.call(cmd)
# TODO: open the file in some editor, any editor instead of giving an error.
except Exception, e:
msg = str(e) + "\n\n" + traceback.format_exc()
MessageBox(text=msg, caption=cmd)
sys.exit(-1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment