Created
July 17, 2017 11:10
-
-
Save osteele/a05c6be5accf2c1f881c073791c1b206 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SpaceTranslationPatch patches the MacOS Python IDE to convert spaces to tabs | |
# when a source file is opened, and back to spaces when it's saved. | |
# | |
# Drop this file on PythonIDE to install it. It requires | |
# http://www.strout.net/python/mac/PatchUtils.py in order to run. | |
# | |
# Version 2 released to the public domain 3 November 1999 | |
# by Oliver Steele (steele@cs.brandeis.edu). | |
""" SpaceTranslationPatch patch | |
Patches the MacOS Python IDE to convert spaces to tabs when a source | |
file is opened, and back to spaces when it's saved. | |
A file is converted if it contains any line (other than the first) that starts with | |
at least four spaces. If the file was saved by the MacOS Python IDE with a | |
tabsize of n spaces set in the font settings dialog, n spaces are converted | |
to one tab. Otherwise four is used for n, except that eight is used if no | |
line starts with four spaces and at least one line starts with eight. Only | |
initial spaces are converted to tabs when a file is opened, and only initial | |
tabs are converted back to spaces when it's written. | |
If you find any files that this heuristic fails for, you're welcome to send them | |
to me, and if I have any free time, I'll improve the heuristic. | |
INSTALLATION INSTRUCTIONS | |
Retrieve http://www.strout.net/python/mac/PatchUtils.py, put it in the | |
Python search path or in the same directory as this file, and drag this file | |
onto PythonInterpreter. | |
BUT FIRST: open both this file and PatchUtils.py, to verify that your download | |
program has successfully converted their line endings into MacOS form. (Otherwise | |
this file will be one large comment line, and do nothing.) | |
""" | |
# To do: | |
# - auto-detect 2-space indents | |
# - read emacs file variables | |
# - let you save a new buffer as space-indented | |
# - let you see whether the file you're editing is space-indented | |
# | |
# Change history: | |
# 11/3/99 v. 2 | |
# - handle mixed tab/space indented files | |
# - don't use the saved tabstop to convert (made the behavior too confusing) | |
# 10/13/99 v. 0.2 | |
# - detect eight-character indented files | |
# - renamed to SpacesTabTranslationPatch | |
# - changed to a patch file, using PatchUtils | |
# - added some (not enough) comments | |
# 8/10/99 | |
# - initial release (released as PyIDESpaceTabTranslationPatch) | |
import os | |
import sys | |
import string | |
from PatchUtils import * | |
basePath = sys.exec_prefix + 'Mac:Tools:IDE:' | |
###################################################################### | |
PyEdit = Patch(basePath, "PyEdit.py", "SpaceTranslationPatch") | |
PyEdit.InsertAfter('_wordchars = string.letters + string.digits + "_"', | |
"""TabifyFilenamePatterns = ['*.py'] # tabify files that match these patterns""") | |
PyEdit.InsertAfter(" self.run_as_main = 0", | |
""" from fnmatch import fnmatch | |
if filter(lambda pattern,path=path,fnmatch=fnmatch:fnmatch(path, pattern), TabifyFilenamePatterns): | |
self.tabify() | |
""") | |
PyEdit.InsertBefore("class _saveoptions:", | |
'''def findFirstNot(s, c): | |
"""Return the index of the first character in s that isn't c. | |
If they're all c, return the length of the string.""" | |
for i in range(len(s)): | |
if s[i] != c: | |
return i | |
return len(s) | |
def computeIndentation(line, tabsize=8): | |
"""Return the indentation of the line, and the offset of the first non-ws character (or the length of the line)""" | |
column, index = 0, 0 | |
while len(line) > index and line[index] in '\\t ': | |
if line[index] == ' ': | |
column = column + 1 | |
else: | |
column = (column + tabsize) / tabsize * tabsize | |
index = index + 1 | |
return column, index | |
def contractInitialSpaces(s, indentation=4, tabsize=8): | |
result = [] | |
for line in string.split(s, '\\r'): | |
column, index = computeIndentation(line, tabsize=tabsize) | |
tabs = column / indentation | |
line = tabs * '\t' + (column - tabs * indentation) * ' ' + line[index:] | |
result.append(line) | |
return string.join(result, '\\r') | |
# string.expandtabs expands all the tabs, whereas we just want to | |
# expand the tabs at the beginning of each line (I think) | |
def expandInitialTabs(s, indentation=4, tabsize=None): | |
result = [] | |
for line in string.split(s, '\\r'): | |
col = findFirstNot(line, '\t') | |
spaces = col * indentation | |
tabs = 0 | |
if spaces: | |
if tabsize: | |
tabs = spaces / tabsize | |
spaces = spaces - tabs * tabsize | |
line = tabs * '\\t' + spaces * ' ' + line[col:] | |
result.append(line) | |
return string.join(result, '\\r') | |
def guessIndentation(text): | |
"""Guess the indentation and tabsize for the text, based on how its lines are indented.""" | |
if string.find(text, '\\r\\t ') >= 0: | |
return 4, 8 | |
# Collect some simple statistics, to decide how many spaces make an indentation level. | |
# The statistics are more general than what we now use, since it's simplest to collect them | |
# that way, and since I'm expecting that we may want to tune the heuristics to attend to more | |
# of them. | |
indentations = [0] * 9 | |
tabsize = 8 | |
for line in string.split(text, '\\r'): | |
indentation = computeIndentation(line, tabsize=tabsize)[0] | |
if indentation < len(indentations): | |
indentations[indentation] = indentations[indentation] + 1 | |
# Assume four spaces per tab, unless there's an indication that it's otherwise." | |
indentation = 4 | |
# If there aren't any lines at 4 and there are at 8, then assume it's 8-stopped." | |
if indentations[4] == 0 and indentations[8] > 0: | |
indentation = 8 | |
if string.find(text, '\\t') < 0: | |
tabsize = None | |
return indentation, tabsize | |
''') | |
PyEdit.ReplaceLine(" data = self.editgroup.editor.get()", | |
""" data = self.untabifiedtext()""") | |
PyEdit.InsertBefore(" def readwindowsettings(self):", | |
''' def tabify(self): | |
"""A file is ripe for tabifying if any line starts with at least four spaces. | |
(The following code ignores the first line, but if that's the only indented line we | |
probably shouldn't be considering it anyway.)""" | |
if string.find(self.editgroup.editor.get(), '\\r ') >= 0: | |
self.tabified = 1 # we use this when the file is saved, to change it back | |
indentation, tabsize = guessIndentation(self.editgroup.editor.get()) | |
self.tabsettings = (indentation, 1) | |
self.editgroup.editor.settabsettings(self.tabsettings) | |
self.tabifiedIndentation = indentation | |
self.tabifiedTabSize = tabsize # so we can expand back out when the file is saved | |
self.editgroup.editor.set(contractInitialSpaces(self.editgroup.editor.get(), indentation=indentation, tabsize=tabsize or 8)) | |
def untabifiedtext(self): | |
"""Retrieve the editor's text, with initial tabs replaced by spaces if it was tabified | |
when it was read from a file.""" | |
data = self.editgroup.editor.get() | |
if hasattr(self, 'tabified'): | |
tabsize = self.tabifiedTabSize | |
indentation = self.tabifiedIndentation | |
# if the indentation has been changed, and it's one that we recognize, use that instead. | |
if self.tabsettings[1] and self.tabsettings[0] in (4,8): | |
indentation = self.tabsettings[0] | |
data = expandInitialTabs(data, indentation=indentation, tabsize=tabsize) | |
return data | |
''') | |
###################################################################### | |
# All files have been patched in memory, so write to disk. | |
PyEdit.Write() | |
print "\nQuit the IDE and restart it to use the new functionality." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment