-
Notifications
You must be signed in to change notification settings - Fork 2
/
ClangFormat.py
executable file
·176 lines (143 loc) · 6.12 KB
/
ClangFormat.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
# This file is a minimal clang-format sublime-integration. To install:
# - Put this file into your sublime Packages directory, e.g. on Linux:
# ~/.config/sublime-text-2/Packages/User/clang-format-sublime.py
# - Add a key binding:
# { "keys": ["ctrl+shift+c"], "command": "clang_format" },
#
# With this integration you can press the bound key and clang-format will
# format the current lines and selections for all cursor positions. The lines
# or regions are extended to the next bigger syntactic entities.
#
# It operates on the current, potentially unsaved buffer and does not create
# or save any files. To revert a formatting, just undo.
import os
import sys
import sublime
import sublime_plugin
import subprocess
import threading
from sublime_lib import ActivityIndicator
PREF_CLANG_FORMAT_PATH = 'clang_format_path'
PREF_FILE_NAME = 'ClangFormat (%s).sublime-settings'
MISSING_BINARY_MESSAGE = 'ClangFormat\n\nTo format the code, either full path to the \
clang-format binary must be specified in the package settings or %s binary must be in the PATH!'
def start_thread(on_exit, on_error, popen_args, stdin):
"""
Runs the given args in a subprocess.Popen, and then calls the function
on_exit when the subprocess completes.
on_exit is a callable object, and popen_args is a list/tuple of args that
on_error when the subprocess throws an error
would give to subprocess.Popen.
"""
def run_in_thread(on_exit, on_error, popen_args):
startupinfo = None
# Don't let console window pop-up on Windows.
if platform_name() == 'windows':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
process = subprocess.Popen(popen_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=startupinfo)
output, error = process.communicate(stdin)
if error:
on_error(error)
else:
on_exit(output)
thread = threading.Thread(target=run_in_thread, args=(on_exit, on_error, popen_args))
thread.start()
# returns immediately after the thread starts
return thread
def platform_name():
if 'linux' in sys.platform:
return 'linux'
elif 'darwin' in sys.platform:
return 'mac'
return 'windows'
def settings_filename():
if 'linux' in sys.platform:
return PREF_FILE_NAME % 'Linux'
elif 'darwin' in sys.platform:
return PREF_FILE_NAME % 'OSX'
return PREF_FILE_NAME % 'Windows'
def binary_name():
if 'win32' in sys.platform:
return 'clang-format.exe'
return 'clang-format'
# Change this to format according to other formatting styles
# (see clang-format -help).
style = 'Chromium'
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
def which(program):
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
class ClangFormatCommand(sublime_plugin.TextCommand):
def __init__(self, view):
super().__init__(view)
self._indicator = None
def run(self, edit, only_selection=True):
settings = sublime.load_settings(settings_filename())
binary_path = settings.get(PREF_CLANG_FORMAT_PATH)
if not binary_path:
binary_path = which(binary_name())
if not binary_path or not is_exe(binary_path):
sublime.message_dialog(MISSING_BINARY_MESSAGE % binary_name())
return
args = [binary_path, '-fallback-style', style]
if self.view.file_name():
args.extend(['-assume-filename', self.view.file_name()])
else:
print('Checking style without knowing file type. Results might be innacurate!')
if only_selection:
for region in self.view.sel():
region_offset = min(region.a, region.b)
region_length = abs(region.b - region.a)
args.extend(['-offset', str(region_offset), '-length', str(region_length)])
buffer_text = self.view.substr(sublime.Region(0, self.view.size()))
encoding = self.view.encoding()
encoding = encoding if encoding != 'Undefined' else 'utf-8'
stdin = buffer_text.encode(encoding)
viewport_pos = self.view.viewport_position()
# Show progress indicator if formatting takes longer than 1s.
self._indicator = ActivityIndicator(self.view, 'ClangFormat: Formatting...')
sublime.set_timeout(self.start_indicator, 1000)
start_thread(
lambda output: self.on_formatting_success(viewport_pos, output, encoding),
self.on_formatting_error,
args,
stdin
)
def on_formatting_success(self, viewport_pos, output, encoding):
self.stop_indicator()
self.view.run_command('clang_format_apply', {
'output': output.decode(encoding),
'viewport_pos': viewport_pos,
})
def on_formatting_error(self, error):
self.stop_indicator()
self.view.window().status_message('ClangFormat: Formatting error: %s' % error)
def start_indicator(self):
if self._indicator:
self._indicator.start()
def stop_indicator(self):
if self._indicator:
self._indicator.stop()
self._indicator = None
class ClangFormatApplyCommand(sublime_plugin.TextCommand):
def run(self, edit, output, viewport_pos):
self.view.window().status_message('ClangFormat: Formatted')
self.view.replace(edit, sublime.Region(0, self.view.size()), output)
# FIXME: Without the 10ms delay, the viewport sometimes jumps.
sublime.set_timeout_async(lambda: self.view.set_viewport_position(viewport_pos, False), 10)