Skip to content

Commit

Permalink
Merge pull request #16 from catch22/style_handling
Browse files Browse the repository at this point in the history
Style handling
  • Loading branch information
jason-kane committed Dec 26, 2015
2 parents b19315c + 47a37f7 commit 8758728
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 104 deletions.
8 changes: 8 additions & 0 deletions Default (Windows).sublime-keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"keys": [
"ctrl+alt+f"
],
"command": "yapf"
}
]
159 changes: 89 additions & 70 deletions PyYapf.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ def smart_failure(self, in_failure):
"""
err, msg, context_dict = failure_parser(in_failure, self.encoding)

sublime.error_message(
"{0}\n{1}\n\n{2}".format(err, msg, repr(context_dict)))
sublime.error_message("{0}\n{1}\n\n{2}".format(err, msg, repr(
context_dict)))

if 'context' in context_dict:
#"('', (46,44))"
Expand Down Expand Up @@ -148,15 +148,7 @@ def smart_failure(self, in_failure):
if self.debug:
print(repr(in_failure))

def save_selection_to_tempfile(self, selection):
"""
dump the current selection to a tempfile
and return the filename. caller is responsible
for cleanup.
"""
fobj, filename = tempfile.mkstemp(suffix=".py")
temphandle = os.fdopen(fobj, 'wb' if PY3 else 'w')

def encode_selection(self, selection):
try:
encoded = self.view.substr(selection).encode(self.encoding)
except UnicodeEncodeError as err:
Expand All @@ -173,16 +165,14 @@ def save_selection_to_tempfile(self, selection):
self.indent, _, _ = line.partition(codeline)
detected = True
unindented.append(line[len(self.indent):])

temphandle.write(b''.join(unindented))
temphandle.close()
return filename
unindented = b''.join(unindented)
return unindented

def replace_selection(self, edit, selection, output):
reindented = []
indent = self.indent.decode(self.encoding)
for line in output.splitlines(keepends=True):
reindented.append(indent + line)
reindented = []
for line in output.splitlines():
reindented.append(indent + line + '\n')
self.view.replace(edit, selection, ''.join(reindented))

def run(self, edit):
Expand All @@ -197,73 +187,102 @@ def run(self, edit):

if self.encoding == "Undefined":
print('Encoding is not specified.')
self.encoding = settings.get('default_encoding', 'UTF-8')
self.encoding = settings.get('default_encoding')

print('Using encoding of %r' % self.encoding)

self.debug = settings.get('debug', False)
self.debug = settings.get('debug')

# there is always at least one region
for region in self.view.sel():
# determine selection to format
if region.empty():
if settings.get("use_entire_file_if_no_selection", True):
if settings.get("use_entire_file_if_no_selection"):
selection = sublime.Region(0, self.view.size())
else:
sublime.error_message('A selection is required')
selection = None
continue
else:
selection = region

if selection:
py_filename = self.save_selection_to_tempfile(selection)

if py_filename:
style_filename = save_style_to_tempfile(
settings.get("config", {}))

yapf = os.path.expanduser(
settings.get("yapf_command", "/usr/local/bin/yapf"))

cmd = [yapf, "--style={0}".format(style_filename),
"--verify", "--in-place", py_filename]

print('Running {0}'.format(cmd))
environment = os.environ.copy()
environment['LANG'] = self.encoding
proc = subprocess.Popen(cmd,
stderr=subprocess.PIPE,
env=environment)

output, output_err = proc.communicate()

temphandle = codecs.open(py_filename,
encoding=self.encoding)
output = temphandle.read()
temphandle.close()

if not output_err:
self.replace_selection(edit, selection, output)
else:
try:
if not PY3:
output_err = output_err.encode(self.encoding)
self.smart_failure(output_err)

# Catching too general exception
# pylint: disable=W0703
except Exception as err:
print('Unable to parse error: %r' % err)
if PY3:
output_err = output_err.decode()
sublime.error_message(output_err)

if self.debug:
with open(style_filename) as file_handle:
print(file_handle.read())

os.unlink(py_filename)
# encode selection
encoded_selection = self.encode_selection(selection)
if not encoded_selection:
continue

# determine yapf command
cmd = settings.get("yapf_command")
assert cmd, "yapf_command not configured"
cmd = os.path.expanduser(cmd)
args = [cmd]

# verify reformatted code
args += ["--verify"]

# override style?
if settings.has('config'):
custom_style = settings.get("config")
style_filename = save_style_to_tempfile(custom_style)
args += ["--style={0}".format(style_filename)]

if self.debug:
print('Using custom style:')
with open(style_filename) as file_handle:
print(file_handle.read())
else:
style_filename = None

# use directory of current file so that custom styles are found properly
fname = self.view.file_name()
cwd = os.path.dirname(fname) if fname else None

# specify encoding in environment
env = os.environ.copy()
env['LANG'] = self.encoding

# win32: hide console window
if sys.platform in ('win32', 'cygwin'):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags = subprocess.CREATE_NEW_CONSOLE | subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
else:
startupinfo = None

# run yapf
print('Running {0} in {1}'.format(args, cwd))
if self.debug:
print('Environment: {0}'.format(env))
popen = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
cwd=cwd,
env=env,
startupinfo=startupinfo)
output, output_err = popen.communicate(encoded_selection)

# handle errors (since yapf>=0.3: exit code 2 means changed, not error)
if popen.returncode not in (0, 2):
try:
if not PY3:
output_err = output_err.encode(self.encoding)
self.smart_failure(output_err)

# Catching too general exception
# pylint: disable=W0703
except Exception as err:
print('Unable to parse error: %r' % err)
if PY3:
output_err = output_err.decode()
sublime.error_message(output_err)
else:
output = output.decode(self.encoding)
self.replace_selection(edit, selection, output)

if style_filename:
os.unlink(style_filename)

# restore cursor
print('restoring cursor to ', region, repr(region))
self.view.show_at_center(region)

Expand Down
131 changes: 97 additions & 34 deletions PyYapf.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,69 @@
// full path and command to run yapf
"yapf_command": "/usr/local/bin/yapf",

// reformat entire file if no text is selected
"use_entire_file_if_no_selection": true,

// add extra output to the console for debugging pyyapf/yapf behavior
"debug": false,

// if no encoding is specified use this. utf-8 is a good choice,
// ascii is (much) more restrictive. Any of these should work:
// ascii is (much) more restrictive. any of these should work:
// https://docs.python.org/2/library/codecs.html#standard-encodings
"default_encoding": "UTF-8",

// yapf style options
// custom yapf style options
//
// if commented out then yapf will search for the formatting style in the following manner:
// 1. in the [style] section of a .style.yapf file in either the current directory or one of its parent directories.
// 2. in the [yapf] section of a setup.cfg file in either the current directory or one of its parent directories.
// 3. in the ~/.config/yapf/style file in your home directory.
// if none of those files are found, the default style is used (PEP8).
//
/*
"config": {
// Determines which of the predefined styles this custom style is based on.
"based_on_style": "pep8",
// Align closing bracket with visual indentation.
"ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT": true,
// Insert a blank line before a 'def' or 'class' immediately
// nested within another 'def' or 'class'.
//
// For example:
//
// class Foo:
// # <------ this blank line
// def method():
// ...
//
"BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF": false,
// The column limit.
"COLUMN_LIMIT": 79,
// Indent width for line continuations.
"CONTINUATION_INDENT_WIDTH": 4,
// Put closing brackets on a separate line, dedented, if the
// bracketed expression can't fit in a single line. Applies to
// all kinds of brackets, including function definitions and calls.
//
// For example:
//
// config = {
// 'key1': 'value1',
// 'key2': 'value2',
// } # <--- this bracket is dedented and on a separate line
//
// time_series = self.remote_client.query_entity_counters(
// entity='dev3246.region1',
// key='dns.query_latency_tcp',
// transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
// start_ts=now()-timedelta(days=3),
// end_ts=now(),
// ) # <--- this bracket is dedented and on a separate line
"DEDENT_CLOSING_BRACKETS": false,
// The regex for an i18n comment. The presence of this comment stops
// reformatting of that line, because the comments are required to be
// next to the string they translate.
Expand All @@ -30,23 +75,23 @@
// away from the i18n comment.
"I18N_FUNCTION_CALL": "",
// The number of columns to use for indentation.
"INDENT_WIDTH": 4,

// Indent width for line continuations.
"CONTINUATION_INDENT_WIDTH": 4,

// Insert a blank line before a 'def' or 'class' immediately nested within
//another 'def' or 'class'.
// Indent the dictionary value if it cannot fit on the same line as the dictionary key.
//
// For example:
//
// class Foo:
// # <------ this blank line
// def method():
// ...
//
"BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF": false,
// config = {
// 'key1':
// 'value1',
// 'key2': value1 +
// value2,
// }
"INDENT_DICTIONARY_VALUE": false,
// The number of columns to use for indentation.
"INDENT_WIDTH": 4,
// Join short lines into one line. E.g., single line if statements.
"JOIN_MULTIPLE_LINES": true,
// Insert a space between the ending comma and closing bracket of a list,
// etc.
Expand All @@ -55,33 +100,51 @@
// The number of spaces required before a trailing comment.
"SPACES_BEFORE_COMMENT": 2,
// Set to True to prefer splitting before &, | or ^ rather than after.
"SPLIT_BEFORE_BITWISE_OPERATOR": true,
// Set to True to prefer splitting before 'and' or 'or' rather than
// after.
"SPLIT_BEFORE_LOGICAL_OPERATOR": false,
// Split named assignments onto individual lines.
"SPLIT_BEFORE_NAMED_ASSIGNS": true,
// The penalty for splitting right after the opening bracket.
"SPLIT_PENALTY_AFTER_OPENING_BRACKET": 30,
// The penalty for splitting the line after a unary operator.
"SPLIT_PENALTY_AFTER_UNARY_OPERATOR": 100,
"SPLIT_PENALTY_AFTER_UNARY_OPERATOR": 10000,
// The penalty of splitting the line around the &, |, and ^ operators.
"SPLIT_PENALTY_BITWISE_OPERATOR": 300,
// The penalty for characters over the column limit.
"SPLIT_PENALTY_EXCESS_CHARACTER": 200,
"SPLIT_PENALTY_EXCESS_CHARACTER": 2500,
// The penalty of splitting the line around the 'and' and 'or' operators.
"SPLIT_PENALTY_LOGICAL_OPERATOR": 30,
// The penalty incurred by adding a line split to the unwrapped line. The
// more line splits added the higher the penalty.
"SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT": 30,
// The penalty for not matching the splitting decision for the matching
// bracket tokens. For instance, if there is a newline after the opening
// bracket, we would tend to expect one before the closing bracket, and
// vice versa.
//"SPLIT_PENALTY_MATCHING_BRACKET": 50,
// The penalty of splitting a list of import as names.
//
// For example:
//
// from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,
// long_argument_2,
// long_argument_3)
//
// would reformat to something like:
//
// from a_very_long_or_indented_module_name_yada_yad import (
// long_argument_1, long_argument_2, long_argument_3)
"SPLIT_PENALTY_IMPORT_NAMES": 0,
// The penalty for splitting right after the opening bracket.
"SPLIT_PENALTY_AFTER_OPENING_BRACKET": 30,
// The penalty of splitting the line around the 'and' and 'or' operators.
"SPLIT_PENALTY_LOGICAL_OPERATOR": 300,
},
*/

// The penalty incurred by adding a line split to the unwrapped line. The
// more line splits added the higher the penalty.
"SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT": 30
}
// add extra output to the console for debugging pyyapf/yapf behavior
"debug": false,
}

0 comments on commit 8758728

Please sign in to comment.