-
Notifications
You must be signed in to change notification settings - Fork 52
/
clang_format.py
338 lines (281 loc) · 12.5 KB
/
clang_format.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
import sublime, sublime_plugin
import subprocess, os
# The styles available by default. We add one option: "Custom". This tells
# the plugin to look in an ST settings file to load the customised style.
styles = ["LLVM", "Google", "Chromium", "Mozilla", "WebKit", "Custom", "File"]
# Settings file locations.
settings_file = 'clang_format.sublime-settings'
custom_style_settings = 'clang_format_custom.sublime-settings'
# Hacky, but there doesn't seem to be a cleaner way to do this for now.
# We need to be able to load all these settings from the settings file.
all_settings = [
"BasedOnStyle", "AccessModifierOffset", "AlignAfterOpenBracket",
"AlignConsecutiveAssignments", "AlignConsecutiveDeclarations",
"AlignEscapedNewlinesLeft", "AlignOperands", "AlignTrailingComments",
"AllowAllParametersOfDeclarationOnNextLine",
"AllowShortBlocksOnASingleLine", "AllowShortCaseLabelsOnASingleLine",
"AllowShortFunctionsOnASingleLine", "AllowShortIfStatementsOnASingleLine",
"AllowShortLoopsOnASingleLine", "AlwaysBreakAfterDefinitionReturnType",
"AlwaysBreakAfterReturnType", "AlwaysBreakBeforeMultilineStrings",
"AlwaysBreakTemplateDeclarations", "BinPackArguments", "BinPackParameters",
"BraceWrapping", "BreakAfterJavaFieldAnnotations", "BreakBeforeBinaryOperators",
"BreakBeforeBraces", "BreakBeforeTernaryOperators",
"BreakConstructorInitializersBeforeComma", "ColumnLimit", "CommentPragmas",
"ConstructorInitializerAllOnOneLineOrOnePerLine",
"ConstructorInitializerIndentWidth", "ContinuationIndentWidth",
"Cpp11BracedListStyle", "DerivePointerAlignment", "DisableFormat",
"ExperimentalAutoDetectBinPacking", "ForEachMacros", "IncludeCategories",
"IndentCaseLabels", "IndentWidth", "IndentWrappedFunctionNames",
"KeepEmptyLinesAtTheStartOfBlocks", "Language", "MacroBlockBegin", "MacroBlockEnd",
"MaxEmptyLinesToKeep", "NamespaceIndentation", "ObjCBlockIndentWidth",
"ObjCSpaceAfterProperty", "ObjCSpaceBeforeProtocolList",
"PenaltyBreakBeforeFirstCallParameter", "PenaltyBreakComment",
"PenaltyBreakFirstLessLess", "PenaltyBreakString",
"PenaltyExcessCharacter", "PenaltyReturnTypeOnItsOwnLine", "PointerAlignment",
"SpaceAfterCStyleCast", "SpaceBeforeAssignmentOperators", "SpaceBeforeParens",
"SpaceInEmptyParentheses", "SpacesBeforeTrailingComments", "SpacesInAngles",
"SpacesInCStyleCastParentheses", "SpacesInContainerLiterals",
"SpacesInParentheses", "SpacesInSquareBrackets", "Standard", "SortIncludes",
"TabWidth", "UseTab"
]
st_encodings_trans = {
"UTF-8" : "utf-8",
"UTF-8 with BOM" : "utf-8-sig",
"UTF-16 LE" : "utf-16-le",
"UTF-16 LE with BOM" : "utf-16",
"UTF-16 BE" : "utf-16-be",
"UTF-16 BE with BOM" : "utf-16",
"Western (Windows 1252)" : "cp1252",
"Western (ISO 8859-1)" : "iso8859-1",
"Western (ISO 8859-3)" : "iso8859-3",
"Western (ISO 8859-15)" : "iso8859-15",
"Western (Mac Roman)" : "mac-roman",
"DOS (CP 437)" : "cp437",
"Arabic (Windows 1256)" : "cp1256",
"Arabic (ISO 8859-6)" : "iso8859-6",
"Baltic (Windows 1257)" : "cp1257",
"Baltic (ISO 8859-4)" : "iso8859-4",
"Celtic (ISO 8859-14)" : "iso8859-14",
"Central European (Windows 1250)" : "cp1250",
"Central European (ISO 8859-2)" : "iso8859-2",
"Cyrillic (Windows 1251)" : "cp1251",
"Cyrillic (Windows 866)" : "cp866",
"Cyrillic (ISO 8859-5)" : "iso8859-5",
"Cyrillic (KOI8-R)" : "koi8-r",
"Cyrillic (KOI8-U)" : "koi8-u",
"Estonian (ISO 8859-13)" : "iso8859-13",
"Greek (Windows 1253)" : "cp1253",
"Greek (ISO 8859-7)" : "iso8859-7",
"Hebrew (Windows 1255)" : "cp1255",
"Hebrew (ISO 8859-8)" : "iso8859-8",
"Nordic (ISO 8859-10)" : "iso8859-10",
"Romanian (ISO 8859-16)" : "iso8859-16",
"Turkish (Windows 1254)" : "cp1254",
"Turkish (ISO 8859-9)" : "iso8859-9",
"Vietnamese (Windows 1258)" : "cp1258",
"Hexadecimal" : None,
"Undefined" : None
}
# Check if we are running on a Windows operating system
os_is_windows = os.name == 'nt'
# The default name of the clang-format executable
default_binary = 'clang-format.exe' if os_is_windows else 'clang-format'
# This function taken from Stack Overflow response:
# http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
def which(program):
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
# Set the path to the binary in the settings file.
def set_path(path):
settings = sublime.load_settings(settings_file)
settings.set('binary', path)
sublime.save_settings(settings_file)
# Make sure the globals are updated.
load_settings()
# We avoid dependencies on yaml, since the output we need is very simple.
def dic_to_yaml_simple(d):
output = ""
n = len(d)
for k in d:
output += str(k)
output += ": "
if type(d[k]) is bool:
output += str(d[k]).lower()
elif type(d[k]) is dict:
output += "{" + dic_to_yaml_simple(d[k]) + "}"
else:
output += str(d[k])
n -= 1
if (n!=0):
output += ', '
return output
# We store a set of customised values in a sublime settings file, so that it is
# possible to very quickly customise the output.
# This function returns the correct customised style tag.
def load_custom():
custom_settings = sublime.load_settings(custom_style_settings)
keys = dict()
for v in all_settings:
result = custom_settings.get(v, None)
if result != None:
keys[v] = result
out = "-style={" + dic_to_yaml_simple(keys) + "}"
return out
# Display input panel to update the path.
def update_path():
load_settings()
w = sublime.active_window()
w.show_input_panel("Path to clang-format: ", binary, set_path, None, None)
# Check that the binary can be found and is executable.
def check_binary():
# If we couldn't find the binary.
if (which(binary) == None):
# Try to guess the correct setting.
if (which(default_binary) != None):
# Looks like clang-format is in the path, remember that.
set_path(default_binary)
return True
# We suggest setting a new path using an input panel.
msg = "The clang-format binary was not found. Set a new path?"
if sublime.ok_cancel_dialog(msg):
update_path()
return True
else:
return False
return True
# Load settings and put their values into global scope.
# Probably a nicer way of doing this, but it's simple enough and it works fine.
def load_settings():
# We set these globals.
global binary
global style
global format_on_save
global languages
settings_global = sublime.load_settings(settings_file)
settings_local = sublime.active_window().active_view().settings().get('ClangFormat', {})
load = lambda name, default: settings_local.get(name, settings_global.get(name, default))
# Load settings, with defaults.
binary = load('binary', default_binary)
style = load('style', styles[0])
format_on_save = load('format_on_save', False)
languages = load('languages', ['C', 'C++', 'C++11', 'JavaScript'])
def is_supported(lang):
load_settings()
return any((lang.endswith((l + '.tmLanguage', l + '.sublime-syntax')) for l in languages))
# Triggered when the user runs clang format.
class ClangFormatCommand(sublime_plugin.TextCommand):
def run(self, edit, whole_buffer=False):
load_settings()
if not check_binary():
return
sublime.status_message("Clang format (style: "+ style + ")." )
# The below code has been taken and tweaked from llvm.
encoding = st_encodings_trans[self.view.encoding()]
if encoding is None:
encoding = 'utf-8'
# We use 'file' not 'File' when passing to the binary.
# But all the other styles are in all caps.
_style = style
if style == "File":
_style = "file"
command = []
if style == "Custom":
command = [binary, load_custom()]
else:
command = [binary, '-style', _style]
regions = []
if whole_buffer:
regions = [sublime.Region(0, self.view.size())]
else:
regions = self.view.sel()
for region in regions:
region_offset = region.begin()
region_length = region.size()
view = sublime.active_window().active_view()
# If the command is run at the end of the line,
# Run the command on the whole line.
if view.classify(region_offset) & sublime.CLASS_LINE_END > 0:
region = view.line(region_offset)
region_offset = region.begin()
region_lenth = region.size()
command.extend(['-offset', str(region_offset),
'-length', str(region_length)])
# We only set the offset once, otherwise CF complains.
command.extend(['-assume-filename', str(self.view.file_name())] )
# TODO: Work out what this does.
# command.extend(['-output-replacements-xml'])
# Run CF, and set buf to its output.
buf = self.view.substr(sublime.Region(0, self.view.size()))
startupinfo = None
if os_is_windows:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
p = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stdin=subprocess.PIPE,
startupinfo=startupinfo)
output, error = p.communicate(buf.encode(encoding))
# Display any errors returned by clang-format using a message box,
# instead of just printing them to the console. Also, we halt on all
# errors: e.g. We don't just settle for using using a default style.
if error:
# We don't want to do anything by default.
# If the error message tells us it is doing that, truncate it.
default_message = ", using LLVM style"
msg = error.decode("utf-8")
if msg.strip().endswith(default_message):
msg = msg[:-len(default_message)-1]
sublime.error_message("Clang format: " + msg)
# Don't do anything.
return
# If there were no errors, we replace the view with the outputted buf.
# Temporarily disable tabs to space so that tabs elsewhere in the file
# do not get modified if they were not part of the formatted selection
prev_tabs_to_spaces = self.view.settings().get('translate_tabs_to_spaces')
self.view.settings().set('translate_tabs_to_spaces', False)
self.view.replace(
edit, sublime.Region(0, self.view.size()),
output.decode(encoding))
# Re-enable previous tabs to space setting
self.view.settings().set('translate_tabs_to_spaces', prev_tabs_to_spaces)
# TODO: better semantics for re-positioning cursors!
# Hook for on-save event, to allow application of clang-format on save.
class clangFormatEventListener(sublime_plugin.EventListener):
def on_pre_save(self, view):
# Only do this for supported languages
syntax = view.settings().get('syntax')
if is_supported(syntax):
# Ensure that settings are up to date.
load_settings()
if format_on_save:
print("Auto-applying Clang Format on save.")
view.run_command("clang_format", {"whole_buffer": True})
# Called from the UI to update the path in the settings.
class clangFormatSetPathCommand(sublime_plugin.WindowCommand):
def run(self):
update_path()
# Called from the UI to set the current style.
class clangFormatSelectStyleCommand(sublime_plugin.WindowCommand):
def done(self, i):
settings = sublime.load_settings(settings_file)
settings.set("style", styles[i])
sublime.save_settings(settings_file)
def run(self):
load_settings()
active_window = sublime.active_window()
# Get current style
try:
sel = styles.index(style)
except ValueError:
sel = 0
active_window.show_quick_panel(styles, self.done, 0, sel)