├── .gitignore
├── .gitmodules
├── .no-sublime-package
├── .travis.yml
├── AStyleFormat.py
├── AStyleFormatter.sublime-commands
├── AStyleFormatterLib
├── MergeUtils.py
├── Options.py
├── __init__.py
└── diff_match_patch
│ ├── COPYING
│ ├── README.txt
│ ├── __init__.py
│ ├── python2
│ ├── __init__.py
│ └── diff_match_patch.py
│ └── python3
│ ├── __init__.py
│ └── diff_match_patch.py
├── CHANGELOG.md
├── Context.sublime-menu
├── DONORS.md
├── Default (Linux).sublime-keymap
├── Default (OSX).sublime-keymap
├── Default (Windows).sublime-keymap
├── LICENSE
├── Main.sublime-menu
├── README.md
├── Side Bar.sublime-menu
├── SublimeAStyleFormatter.sublime-settings
├── appveyor.yml
├── dump_default_options
├── messages.json
├── messages
├── 1.3.txt
├── 1.7.1.txt
├── 1.7.3.txt
├── 1.7.txt
├── 1.8.txt
├── 1.9.1.txt
├── 1.9.2.txt
├── 1.9.4.txt
├── 1.9.txt
├── 2.0.0.txt
├── 2.0.2.txt
├── 2.0.3.txt
├── 2.0.4.txt
├── 2.0.5.txt
├── 2.1.0.txt
├── 3.0.0.txt
├── 3.1.0.txt
├── 3.1.1.txt
├── 3.1.2.txt
└── install.txt
├── options_default.json
├── package.json
├── pyastyle
├── __init__.py
├── python2
│ ├── __init__.py
│ ├── _linux_x86
│ │ ├── __init__.py
│ │ └── pyastyle.so
│ ├── _linux_x86_64
│ │ ├── __init__.py
│ │ └── pyastyle.so
│ ├── _local_arch
│ │ └── __init__.py
│ ├── _macosx_universal
│ │ ├── __init__.py
│ │ └── pyastyle.so
│ ├── _win32
│ │ ├── __init__.py
│ │ └── pyastyle.pyd
│ └── _win64
│ │ ├── __init__.py
│ │ └── pyastyle.pyd
└── python3
│ ├── __init__.py
│ ├── _linux_x86
│ ├── __init__.py
│ └── pyastyle.cpython-33m.so
│ ├── _linux_x86_64
│ ├── __init__.py
│ └── pyastyle.cpython-33m.so
│ ├── _local_arch
│ └── __init__.py
│ ├── _macosx_universal
│ ├── __init__.py
│ └── pyastyle.so
│ ├── _win32
│ ├── __init__.py
│ └── pyastyle.pyd
│ └── _win64
│ ├── __init__.py
│ └── pyastyle.pyd
├── src
├── Dependencies.txt
├── _build.sh
├── user_build_py2.sh
├── user_build_py3.sh
└── win_build_all.bat
├── tests
└── test.py
└── unittesting.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | *.cache
4 | *.sublime-project
5 | *.swp
6 | package-metadata.json
7 | __pycache__/
8 | _local_arch/
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/pyastyle"]
2 | path = src/pyastyle
3 | url = git://github.com/timonwong/pyastyle.git
4 |
--------------------------------------------------------------------------------
/.no-sublime-package:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/.no-sublime-package
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - linux
3 | - osx
4 |
5 | env:
6 | global:
7 | - PACKAGE="SublimeAStyleFormatter"
8 | matrix:
9 | - SUBLIME_TEXT_VERSION="2"
10 | - SUBLIME_TEXT_VERSION="3"
11 |
12 | before_install:
13 | - curl -OL https://raw.githubusercontent.com/randy3k/UnitTesting/master/sbin/travis.sh
14 |
15 | install:
16 | - sh travis.sh bootstrap
17 |
18 | script:
19 | - sh travis.sh run_tests
20 |
21 | notifications:
22 | email:
23 | on_success: never
24 | on_failure: always
25 |
--------------------------------------------------------------------------------
/AStyleFormat.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2012 Timon Wong
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 | of the Software, and to permit persons to whom the Software is furnished to do
9 | so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 | """
22 |
23 | import sublime
24 | import sublime_plugin
25 | import json
26 | import re
27 | import os
28 | import sys
29 |
30 | if sys.version_info < (3, 0):
31 | import pyastyle
32 | from AStyleFormatterLib import get_syntax_mode_mapping, Options
33 | from AStyleFormatterLib.MergeUtils import merge_code
34 | else:
35 | from . import pyastyle
36 | from .AStyleFormatterLib import get_syntax_mode_mapping, Options
37 | from .AStyleFormatterLib.MergeUtils import merge_code
38 |
39 |
40 | __file__ = os.path.normpath(os.path.abspath(__file__))
41 | __path__ = os.path.dirname(__file__)
42 |
43 | PLUGIN_NAME = 'SublimeAStyleFormatter'
44 | SYNTAX_RE = re.compile(r'(?<=source\.)[\w+#]+')
45 |
46 | with open(os.path.join(__path__, 'options_default.json')) as fp:
47 | OPTIONS_DEFAULT = json.load(fp)
48 |
49 |
50 | def log(level, fmt, args):
51 | s = PLUGIN_NAME + ': [' + level + '] ' + (fmt.format(*args))
52 | print(s)
53 |
54 |
55 | def log_debug(fmt, *args):
56 | log('DEBUG', fmt, args)
57 |
58 |
59 | def load_settings():
60 | return sublime.load_settings(PLUGIN_NAME + '.sublime-settings')
61 |
62 |
63 | _VARPROG_RE = re.compile(r'\$(\w+|\{[^}]*\})')
64 |
65 |
66 | def custom_expandvars(path, custom_envs):
67 | if '$' not in path:
68 | return path
69 | envs = custom_envs.copy()
70 | envs.update(os.environ)
71 | i = 0
72 | while True:
73 | m = _VARPROG_RE.search(path, i)
74 | if not m:
75 | break
76 | i, j = m.span(0)
77 | name = m.group(1)
78 | if name.startswith('{') and name.endswith('}'):
79 | name = name[1:-1]
80 | if name in envs:
81 | tail = path[j:]
82 | path = path[:i] + envs[name]
83 | i = len(path)
84 | path += tail
85 | else:
86 | i = j
87 | return path
88 |
89 |
90 | def get_settings_for_view(view, key, default=None):
91 | try:
92 | settings = view.settings()
93 | sub_key = 'AStyleFormatter'
94 | if settings.has(sub_key):
95 | proj_settings = settings.get(sub_key)
96 | if key in proj_settings:
97 | return proj_settings[key]
98 | except:
99 | pass
100 | settings = load_settings()
101 | return settings.get(key, default)
102 |
103 |
104 | def get_settings_for_active_view(key, default=None):
105 | return get_settings_for_view(
106 | sublime.active_window().active_view(), key, default)
107 |
108 |
109 | def get_syntax_for_view(view):
110 | caret = view.sel()[0].a
111 | syntax = SYNTAX_RE.search(view.scope_name(caret))
112 | if syntax is None:
113 | return ''
114 | return syntax.group(0).lower()
115 |
116 |
117 | def is_supported_syntax(view, syntax):
118 | mapping = get_settings_for_view(
119 | view, 'user_defined_syntax_mode_mapping', {})
120 | return syntax in get_syntax_mode_mapping(mapping)
121 |
122 |
123 | def is_enabled_in_view(view):
124 | syntax = get_syntax_for_view(view)
125 | return is_supported_syntax(view, syntax)
126 |
127 |
128 | class AstyleformatCommand(sublime_plugin.TextCommand):
129 | def _get_settings(self, key, default=None):
130 | return get_settings_for_view(self.view, key, default=default)
131 |
132 | def _get_syntax_settings(self, syntax, formatting_mode):
133 | key = 'options_%s' % formatting_mode
134 | settings = get_settings_for_view(self.view, key, default={})
135 | if syntax and syntax != formatting_mode:
136 | key = 'options_%s' % syntax
137 | settings_override = get_settings_for_view(
138 | self.view, key, default={})
139 | settings.update(settings_override)
140 | return settings
141 |
142 | def _get_default_options(self):
143 | options_default = OPTIONS_DEFAULT.copy()
144 | options_default_override = self._get_settings(
145 | 'options_default', default={})
146 | options_default.update(options_default_override)
147 | return options_default
148 |
149 | _SKIP_COMMENT_RE = re.compile(r'\s*\#')
150 |
151 | def _build_custom_vars(self):
152 | view = self.view
153 | custom_vars = {
154 | 'packages': sublime.packages_path(),
155 | }
156 | full_path = view.file_name()
157 | if full_path:
158 | file_name = os.path.basename(full_path)
159 | file_base_name, file_extension = os.path.splitext(file_name)
160 | custom_vars.update({
161 | 'file_path': os.path.dirname(full_path),
162 | 'file': full_path,
163 | 'file_name': file_name,
164 | 'file_extension': file_extension,
165 | 'file_base_name': file_base_name,
166 | })
167 | if sublime.version() > '3000':
168 | window = view.window()
169 | project_file_name = window.project_file_name()
170 | if project_file_name:
171 | project_name = os.path.basename(project_file_name)
172 | project_base_name, project_extension = os.path.splitext(
173 | project_name)
174 | custom_vars.update({
175 | 'project': project_file_name,
176 | 'project_path': os.path.dirname(project_file_name),
177 | 'project_name': project_name,
178 | 'project_extension': project_extension,
179 | 'project_base_name': project_base_name,
180 | })
181 | return custom_vars
182 |
183 | def _read_astylerc(self, path):
184 | # Expand environment variables first
185 | fullpath = custom_expandvars(path, self._build_custom_vars())
186 | if not os.path.isfile(fullpath):
187 | return ''
188 | try:
189 | lines = []
190 | with open(fullpath, 'r') as f:
191 | for line in f:
192 | if not self._SKIP_COMMENT_RE.match(line):
193 | lines.append(line.strip())
194 | return ' '.join(lines)
195 | except Exception:
196 | return ''
197 |
198 | @staticmethod
199 | def _join_options(options_list):
200 | return Options.strip_invalid_options_string(
201 | ' '.join(o for o in options_list if o))
202 |
203 | def _get_options(self, syntax, formatting_mode):
204 | syntax_settings = self._get_syntax_settings(syntax, formatting_mode)
205 | # --mode=xxx placed first
206 | options_list = [Options.build_astyle_mode_option(formatting_mode)]
207 |
208 | if 'additional_options_file' in syntax_settings:
209 | astylerc_options = self._read_astylerc(
210 | syntax_settings['additional_options_file'])
211 | else:
212 | astylerc_options = ''
213 |
214 | if 'additional_options' in syntax_settings:
215 | additional_options = ' '.join(
216 | syntax_settings['additional_options'])
217 | else:
218 | additional_options = ''
219 |
220 | options_list.append(additional_options)
221 | options_list.append(astylerc_options)
222 |
223 | # Check if user will use only additional options, skip processing other
224 | # options when 'use_only_additional_options' is true
225 | if syntax_settings.get('use_only_additional_options', False):
226 | return self._join_options(options_list)
227 |
228 | # Get default options
229 | default_settings = self._get_default_options()
230 | # Merge syntax_settings with default_settings
231 | default_settings.update(syntax_settings)
232 | options = Options.build_astyle_options(
233 | default_settings,
234 | self._build_indent_options(),
235 | convert_tabs=self._should_convert_tabs()
236 | )
237 | options = ' '.join(options)
238 | options_list.insert(1, options)
239 | return self._join_options(options_list)
240 |
241 | def _build_indent_options(self):
242 | view_settings = self.view.settings()
243 | return {
244 | 'indent': 'spaces'
245 | if view_settings.get('translate_tabs_to_spaces')
246 | else 'tab',
247 | 'spaces': view_settings.get('tab_size'),
248 | }
249 |
250 | def _should_convert_tabs(self):
251 | view_settings = self.view.settings()
252 | return view_settings.get('translate_tabs_to_spaces')
253 |
254 | def _get_formatting_mode(self, syntax):
255 | mapping = get_settings_for_view(
256 | self.view, 'user_defined_syntax_mode_mapping', {})
257 | return get_syntax_mode_mapping(mapping).get(syntax, '')
258 |
259 | def run(self, edit, selection_only=False):
260 | # Close output panel previouslly created each run
261 | under_unittest = self.view.settings().get('_UNDER_UNITTEST')
262 | error_panel = ErrorMessagePanel("astyle_error_message",
263 | under_unittest=under_unittest)
264 | error_panel.close()
265 |
266 | try:
267 | # Loading options
268 | syntax = get_syntax_for_view(self.view)
269 | formatting_mode = self._get_formatting_mode(syntax)
270 | options = self._get_options(syntax, formatting_mode)
271 | except Options.ImproperlyConfigured as e:
272 | extra_message = e.extra_message
273 | error_panel = ErrorMessagePanel("astyle_error_message")
274 | error_panel.write(
275 | "%s: An error occurred while processing options: %s\n\n" % (
276 | PLUGIN_NAME, e))
277 | if extra_message:
278 | error_panel.write("* %s\n" % extra_message)
279 | error_panel.show()
280 | return
281 | # Options ok, format now
282 | try:
283 | if selection_only:
284 | self.run_selection_only(edit, options)
285 | else:
286 | self.run_whole_file(edit, options)
287 | except pyastyle.error as e:
288 | error_panel.write(
289 | "%s: An error occurred while formatting using astyle: %s\n\n"
290 | % (PLUGIN_NAME, e))
291 | error_panel.show()
292 | return
293 | if self._get_settings('debug', False):
294 | log_debug('AStyle version: {0}', pyastyle.version())
295 | log_debug('AStyle options: ' + options)
296 | sublime.status_message('AStyle (v%s) Formatted' % pyastyle.version())
297 |
298 | _STRIP_LEADING_SPACES_RE = re.compile(r'[ \t]*\n([^\r\n])')
299 |
300 | def run_selection_only(self, edit, options):
301 | def get_line_indentation_pos(view, point):
302 | line_region = view.line(point)
303 | pos = line_region.a
304 | end = line_region.b
305 | while pos < end:
306 | ch = view.substr(pos)
307 | if ch != ' ' and ch != '\t':
308 | break
309 | pos += 1
310 | return pos
311 |
312 | def get_indentation_count(view, start):
313 | indent_count = 0
314 | i = start - 1
315 | while i > 0:
316 | ch = view.substr(i)
317 | scope = view.scope_name(i)
318 | # Skip preprocessors, strings, characaters and comments
319 | if ('string.quoted' in scope or
320 | 'comment' in scope or 'preprocessor' in scope):
321 | extent = view.extract_scope(i)
322 | i = extent.a - 1
323 | continue
324 | else:
325 | i -= 1
326 |
327 | if ch == '}':
328 | indent_count -= 1
329 | elif ch == '{':
330 | indent_count += 1
331 | return indent_count
332 |
333 | view = self.view
334 | regions = []
335 | for sel in view.sel():
336 | start = get_line_indentation_pos(view, min(sel.a, sel.b))
337 | region = sublime.Region(
338 | view.line(start).a, # line start of first line
339 | view.line(max(sel.a, sel.b)).b) # line end of last line
340 | indent_count = get_indentation_count(view, start)
341 | # Add braces for indentation hack
342 | text = '{' * indent_count
343 | if indent_count > 0:
344 | text += '\n'
345 | text += view.substr(region)
346 | # Performing astyle formatter
347 | formatted_code = pyastyle.format(text, options)
348 | if indent_count > 0:
349 | for _ in range(indent_count):
350 | index = formatted_code.find('{') + 1
351 | formatted_code = formatted_code[index:]
352 | formatted_code = self._STRIP_LEADING_SPACES_RE.sub(
353 | r'\1', formatted_code, 1)
354 | else:
355 | # HACK: While no identation, a '{' will generate a blank line,
356 | # so strip it.
357 | search = '\n{'
358 | if search not in text:
359 | formatted_code = formatted_code.replace(search, '{', 1)
360 | # Applying formatted text
361 | view.replace(edit, region, formatted_code)
362 | # Region for replaced text
363 | if sel.a <= sel.b:
364 | regions.append(
365 | sublime.Region(region.a, region.a + len(formatted_code)))
366 | else:
367 | regions.append(
368 | sublime.Region(region.a + len(formatted_code), region.a))
369 | view.sel().clear()
370 | # Add regions of formatted text
371 | [view.sel().add(region) for region in regions]
372 |
373 | def run_whole_file(self, edit, options):
374 | view = self.view
375 | region = sublime.Region(0, view.size())
376 | code = view.substr(region)
377 | # Performing astyle formatter
378 | formatted_code = pyastyle.format(code, options)
379 | # Replace to view
380 | _, err = merge_code(view, edit, code, formatted_code)
381 | if err:
382 | error_panel = ErrorMessagePanel("astyle_error_message")
383 | error_panel.write('%s: Merge failure: "%s"\n' % (PLUGIN_NAME, err))
384 |
385 | def is_enabled(self):
386 | return is_enabled_in_view(self.view)
387 |
388 |
389 | class PluginEventListener(sublime_plugin.EventListener):
390 | def on_pre_save(self, view):
391 | if is_enabled_in_view(view) and get_settings_for_active_view(
392 | 'autoformat_on_save', default=False):
393 | view.run_command('astyleformat')
394 |
395 | def on_query_context(self, view, key, operator, operand, match_all):
396 | if key == 'astyleformat_is_enabled':
397 | return is_enabled_in_view(view)
398 | return None
399 |
400 |
401 | class AstylePanelInsertCommand(sublime_plugin.TextCommand):
402 |
403 | def run(self, edit, text):
404 | self.view.set_read_only(False)
405 | self.view.insert(edit, self.view.size(), text)
406 | self.view.set_read_only(True)
407 | self.view.show(self.view.size())
408 |
409 |
410 | class ErrorMessagePanel(object):
411 | def __init__(self, name, under_unittest=False, word_wrap=False,
412 | line_numbers=False, gutter=False, scroll_past_end=False,
413 | syntax='Packages/Text/Plain text.tmLanguage'):
414 | # If we are under testing, do not manipulate output panel
415 | self.name = name
416 | self.window = None
417 | self.output_view = None
418 | if not under_unittest:
419 | self.window = sublime.active_window()
420 | self.output_view = self.window.get_output_panel(name)
421 |
422 | settings = self.output_view.settings()
423 | settings.set("word_wrap", word_wrap)
424 | settings.set("line_numbers", line_numbers)
425 | settings.set("gutter", gutter)
426 | settings.set("scroll_past_end", scroll_past_end)
427 | settings.set("syntax", syntax)
428 |
429 | def write(self, s):
430 | if self.output_view:
431 | self.output_view.run_command('astyle_panel_insert', {'text': s})
432 |
433 | def show(self):
434 | if self.output_view:
435 | self.window.run_command(
436 | "show_panel", {"panel": "output." + self.name})
437 |
438 | def close(self):
439 | if self.output_view:
440 | self.window.run_command(
441 | "hide_panel", {"panel": "output." + self.name})
442 |
--------------------------------------------------------------------------------
/AStyleFormatter.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "SublimeAStyleFormatter: Format Current File",
4 | "command": "astyleformat"
5 | },
6 | {
7 | "caption": "SublimeAStyleFormmatter: Format Current Selection",
8 | "command": "astyleformat",
9 | "args": {"selection_only": true}
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/MergeUtils.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2012 The GoSublime Authors
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 | of the Software, and to permit persons to whom the Software is furnished to do
9 | so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 | """
22 |
23 | # Borrowed from GoSublime
24 |
25 | import sublime
26 |
27 | from .diff_match_patch import diff_match_patch
28 |
29 |
30 | class MergeException(Exception):
31 | pass
32 |
33 |
34 | def _merge_code(view, edit, code, formatted):
35 | def ss(start, end):
36 | return view.substr(sublime.Region(start, end))
37 |
38 | dmp = diff_match_patch()
39 | diffs = dmp.diff_main(code, formatted)
40 | dmp.diff_cleanupEfficiency(diffs)
41 | i = 0
42 | dirty = False
43 | for k, s in diffs:
44 | l = len(s)
45 | if k == 0:
46 | # match
47 | l = len(s)
48 | if ss(i, i + l) != s:
49 | raise MergeException('mismatch', dirty)
50 | i += l
51 | else:
52 | dirty = True
53 | if k > 0:
54 | # insert
55 | view.insert(edit, i, s)
56 | i += l
57 | else:
58 | # delete
59 | if ss(i, i + l) != s:
60 | raise MergeException('mismatch', dirty)
61 | view.erase(edit, sublime.Region(i, i + l))
62 | return dirty
63 |
64 |
65 | def merge_code(view, edit, code, formatted_code):
66 | vs = view.settings()
67 | ttts = vs.get("translate_tabs_to_spaces")
68 | vs.set("translate_tabs_to_spaces", False)
69 | if not code.strip():
70 | return (False, '')
71 |
72 | dirty = False
73 | err = ''
74 | try:
75 | dirty = _merge_code(view, edit, code, formatted_code)
76 | except MergeException as err:
77 | dirty = True
78 | err = "Could not merge changes into the buffer, edit aborted: %s" % err
79 | view.replace(edit, sublime.Region(0, view.size()), code)
80 | except Exception as ex:
81 | err = "Unknown exception: %s" % ex
82 | finally:
83 | vs.set("translate_tabs_to_spaces", ttts)
84 | return (dirty, err)
85 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/Options.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2012-2015 Timon Wong
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 | of the Software, and to permit persons to whom the Software is furnished to do
9 | so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 | """
22 | import shlex
23 |
24 |
25 | class ImproperlyConfigured(Exception):
26 | def __init__(self, option_name, option_value, extra_message=None):
27 | self._name = option_name
28 | self._value = option_value
29 | self.extra_message = extra_message
30 |
31 | def __str__(self):
32 | return "Invalid value '{value}' in option '{name}'".format(
33 | name=self._name, value=self._value)
34 |
35 |
36 | class RangeError(ImproperlyConfigured):
37 | def __init__(self, option_name, option_value, minval, maxval):
38 | super(RangeError, self).__init__(option_name, option_value)
39 | self._minval = minval
40 | self._maxval = maxval
41 |
42 | def __str__(self):
43 | return "Value in option '{name}' should be between '{minval}' and " \
44 | "'{maxval}'".format(name=self._name,
45 | minval=self._minval,
46 | maxval=self._maxval)
47 |
48 |
49 | def ensure_value_range(option_name, value, minval=None, maxval=None):
50 | new_value = value
51 | # Clamp value when out of range
52 | if value < minval:
53 | new_value = minval
54 | elif value > maxval:
55 | new_value = maxval
56 | # Good
57 | if new_value == value:
58 | return
59 |
60 | minval_str = "-Inf" if minval is None else str(minval)
61 | maxval_str = "+Inf" if maxval is None else str(maxval)
62 | raise RangeError(option_name, value, minval_str, maxval_str)
63 |
64 |
65 | def process_option_generic(options, option_name, value):
66 | if value and len(option_name) > 0:
67 | options.append("--{0}".format(option_name))
68 | return options
69 |
70 | STYLE_OPTIONS = set([
71 | "allman", "bsd", "break", "java", "attach", "kr", "k&r", "k/r",
72 | "stroustrup", "whitesmith", "banner", "gnu", "linux", "horstmann", "1tbs",
73 | "otbs", "google", "pico", "lisp", "python", "vtk"])
74 |
75 |
76 | def process_option_style(options, option_name, value):
77 | assert option_name == "style"
78 | if not value:
79 | return options
80 | if value not in STYLE_OPTIONS:
81 | extra_message = None
82 | if value == "ansi":
83 | # Give user a hint about 'ansi' format style
84 | extra_message = \
85 | "'ansi' style is removed from astyle in v2.05, please update" \
86 | " your settings and use 'allman' instead. See " \
87 | "http://astyle.sourceforge.net/news.html for more information."
88 | raise ImproperlyConfigured(option_name, value,
89 | extra_message=extra_message)
90 | options.append("--{0}={1}".format(option_name, value))
91 | return options
92 |
93 |
94 | def process_option_min_conditional_indent(options, option_name, value):
95 | assert option_name == "min-conditional-indent"
96 | if value is None:
97 | return options
98 | ensure_value_range(option_name, value, minval=0, maxval=3)
99 | options.append("--{0}={1}".format(option_name, value))
100 | return options
101 |
102 |
103 | def process_option_max_instatement_indent(options, option_name, value):
104 | assert option_name == "max-instatement-indent"
105 | if value is None:
106 | return options
107 | ensure_value_range(option_name, value, minval=40, maxval=120)
108 | options.append("--{0}={1}".format(option_name, value))
109 | return options
110 |
111 |
112 | def process_option_max_code_length(options, option_name, value):
113 | assert option_name == "max-code-length"
114 | if value is None or value == -1:
115 | return options
116 | ensure_value_range(option_name, value, minval=50, maxval=200)
117 | options.append("--{0}={1}".format(option_name, value))
118 | return options
119 |
120 |
121 | def process_option_break_blocks(options, option_name, value):
122 | assert option_name == "break-blocks"
123 | if not value:
124 | return options
125 | if value not in ("default", "all"):
126 | raise ImproperlyConfigured(option_name, value)
127 | if value == "default":
128 | options.append("--break-blocks")
129 | elif value == "all":
130 | options.append("--break-blocks=all")
131 | return options
132 |
133 |
134 | def process_option_align_pointer(options, option_name, value):
135 | assert option_name == "align-pointer"
136 | if not value:
137 | return options
138 | if value not in ("type", "middle", "name"):
139 | raise ImproperlyConfigured(option_name, value)
140 | options.append("--{0}={1}".format(option_name, value))
141 | return options
142 |
143 |
144 | def process_option_align_reference(options, option_name, value):
145 | assert option_name == "align-reference"
146 | if not value:
147 | return options
148 | if value not in ("none", "type", "middle", "name"):
149 | raise ImproperlyConfigured(option_name, value)
150 | options.append("--{0}={1}".format(option_name, value))
151 | return options
152 |
153 |
154 | def process_option_pad_method_colon(options, option_name, value):
155 | assert option_name == "pad-method-colon"
156 | if not value:
157 | return options
158 | if value not in ("none", "all", "after", "before"):
159 | raise ImproperlyConfigured(option_name, value)
160 | options.append("--{0}={1}".format(option_name, value))
161 | return options
162 |
163 |
164 | OPTION_PROCESSOR_MAP = {
165 | "style": process_option_style,
166 | "indent-classes": process_option_generic,
167 | "indent-modifiers": process_option_generic,
168 | "indent-switches": process_option_generic,
169 | "indent-cases": process_option_generic,
170 | "indent-namespaces": process_option_generic,
171 | "indent-labels": process_option_generic,
172 | "indent-preproc-block": process_option_generic,
173 | "indent-preproc-define": process_option_generic,
174 | "indent-preproc-cond": process_option_generic,
175 | "indent-col1-comments": process_option_generic,
176 | "attach-namespaces": process_option_generic,
177 | "attach-classes": process_option_generic,
178 | "attach-inlines": process_option_generic,
179 | "attach-extern-c": process_option_generic,
180 | "min-conditional-indent": process_option_min_conditional_indent,
181 | "max-instatement-indent": process_option_max_instatement_indent,
182 | "break-blocks": process_option_break_blocks,
183 | "pad-oper": process_option_generic,
184 | "pad-paren": process_option_generic,
185 | "pad-paren-out": process_option_generic,
186 | "pad-first-paren-out": process_option_generic,
187 | "pad-paren-in": process_option_generic,
188 | "pad-header": process_option_generic,
189 | "unpad-paren": process_option_generic,
190 | "delete-empty-lines": process_option_generic,
191 | "fill-empty-lines": process_option_generic,
192 | "break-closing-brackets": process_option_generic,
193 | "break-elseifs": process_option_generic,
194 | "add-brackets": process_option_generic,
195 | "remove-brackets": process_option_generic,
196 | "add-one-line-brackets": process_option_generic,
197 | "keep-one-line-blocks": process_option_generic,
198 | "keep-one-line-statements": process_option_generic,
199 | "close-templates": process_option_generic,
200 | "remove-comment-prefix": process_option_generic,
201 | "max-code-length": process_option_max_code_length,
202 | "break-after-logical": process_option_generic,
203 | "align-pointer": process_option_align_pointer,
204 | "align-reference": process_option_align_reference,
205 | "align-method-colon": process_option_generic,
206 | "pad-method-prefix": process_option_generic,
207 | "unpad-method-prefix": process_option_generic,
208 | "pad-method-colon": process_option_pad_method_colon,
209 | }
210 |
211 |
212 | def build_astyle_mode_option(mode):
213 | if not mode:
214 | return ''
215 | return '--mode=' + mode
216 |
217 |
218 | def special_process_option_indent(options, indent_method, spaces):
219 | if not indent_method:
220 | return options
221 | if indent_method not in ("spaces", "tab", "force-tab", "force-tab-x"):
222 | raise ImproperlyConfigured("indent", "%s:%s" % (indent_method, spaces))
223 | option = '--indent={0}'.format(indent_method)
224 | if spaces is not None:
225 | ensure_value_range("indent=%s" % indent_method, spaces, 2, 20)
226 | option += '={0}'.format(spaces)
227 | options.append(option)
228 | return options
229 |
230 |
231 | def build_astyle_options(settings, indent_options, convert_tabs=False):
232 | options = []
233 | # Special indent option handling
234 | if not settings['indent'] and not settings['indent-spaces']:
235 | settings['indent'] = indent_options['indent']
236 | settings['indent-spaces'] = indent_options['spaces']
237 | options = special_process_option_indent(
238 | options, settings["indent"], settings.get("indent-spaces"))
239 | if convert_tabs:
240 | options.append('--convert-tabs')
241 | for option_name, function in OPTION_PROCESSOR_MAP.items():
242 | if option_name not in settings:
243 | continue
244 | value = settings[option_name]
245 | options = function(options, option_name, value)
246 | return options
247 |
248 |
249 | _BLACK_LIST_MATCH = set([
250 | '-n',
251 | '--recursive', '-r', '-R',
252 | '--dry-run', '--exclude',
253 | '--ignore-exclude-errors', '-i',
254 | '--ignore-exclude-errors-x', '-xi',
255 | '--errors-to-stdout', '-X',
256 | '--preserve-date', '-Z',
257 | '--verbose', '-v',
258 | '--formatted', '-Q',
259 | '--quiet', '-q',
260 | '--lineend', '-z1', '-z2', '-z3',
261 | '--ascii', '-I'
262 | '--version', '-V',
263 | '--help', '-h', '-?',
264 | '--html', '-!',
265 | ])
266 |
267 | _BLACK_LIST_STARTS_WITH = set([
268 | '--suffix=',
269 | '--exclude=',
270 | ])
271 |
272 |
273 | def strip_invalid_options_string(options_string):
274 | options = shlex.split(options_string)
275 | result = []
276 | for option in options:
277 | if option in _BLACK_LIST_MATCH:
278 | continue
279 | if '=' in option:
280 | for item in _BLACK_LIST_STARTS_WITH:
281 | if option.startswith(item):
282 | continue
283 | result.append(option)
284 | return ' '.join(result)
285 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2012 Timon Wong
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 | of the Software, and to permit persons to whom the Software is furnished to do
9 | so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 | """
22 |
23 | SYNTAX_MODE_MAPPING = {
24 | 'c': 'c',
25 | 'c++': 'c',
26 | 'objc': 'c',
27 | 'objc++': 'c',
28 | 'opencl': 'c',
29 | 'cuda-c++': 'c',
30 | 'arduino': 'c',
31 | 'cs': 'cs',
32 | 'java': 'java',
33 | 'pde': 'java',
34 | 'apex': 'java',
35 | }
36 |
37 | SUPPORTED_SYNTAXES = set(SYNTAX_MODE_MAPPING.keys())
38 |
39 |
40 | def get_syntax_mode_mapping(user_defined_syntax_mode_mapping=None):
41 | mapping = SYNTAX_MODE_MAPPING.copy()
42 | if user_defined_syntax_mode_mapping:
43 | mapping.update(user_defined_syntax_mode_mapping)
44 | return mapping
45 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/diff_match_patch/COPYING:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/diff_match_patch/README.txt:
--------------------------------------------------------------------------------
1 | Diff, Match and Patch Library
2 | http://code.google.com/p/google-diff-match-patch/
3 | Neil Fraser
4 |
5 | This library is currently available in seven different ports, all using the same API.
6 | Every version includes a full set of unit tests.
7 |
8 | C++:
9 | * Ported by Mike Slemmer.
10 | * Currently requires the Qt library.
11 |
12 | C#:
13 | * Ported by Matthaeus G. Chajdas.
14 |
15 | Dart:
16 | * The Dart language is still growing and evolving, so this port is only as
17 | stable as the underlying language.
18 |
19 | Java:
20 | * Included is both the source and a Maven package.
21 |
22 | JavaScript:
23 | * diff_match_patch_uncompressed.js is the human-readable version.
24 | Users of node.js should 'require' this uncompressed version since the
25 | compressed version is not guaranteed to work outside of a web browser.
26 | * diff_match_patch.js has been compressed using Google's internal JavaScript compressor.
27 | Non-Google hackers who wish to recompress the source can use:
28 | http://dean.edwards.name/packer/
29 |
30 | Lua:
31 | * Ported by Duncan Cross.
32 | * Does not support line-mode speedup.
33 |
34 | Objective C:
35 | * Ported by Jan Weiss.
36 | * Includes speed test (this is a separate bundle for other languages).
37 |
38 | Python:
39 | * Two versions, one for Python 2.x, the other for Python 3.x.
40 | * Runs 10x faster under PyPy than CPython.
41 |
42 | Demos:
43 | * Separate demos for Diff, Match and Patch in JavaScript.
44 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/diff_match_patch/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | if sys.version_info < (3, 0):
4 | from .python2 import *
5 | else:
6 | from .python3 import *
7 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/diff_match_patch/python2/__init__.py:
--------------------------------------------------------------------------------
1 | from .diff_match_patch import diff_match_patch, patch_obj
2 |
3 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/diff_match_patch/python3/__init__.py:
--------------------------------------------------------------------------------
1 | from .diff_match_patch import diff_match_patch, patch_obj
2 |
3 |
--------------------------------------------------------------------------------
/AStyleFormatterLib/diff_match_patch/python3/diff_match_patch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | """Diff Match and Patch
4 |
5 | Copyright 2006 Google Inc.
6 | http://code.google.com/p/google-diff-match-patch/
7 |
8 | Licensed under the Apache License, Version 2.0 (the "License");
9 | you may not use this file except in compliance with the License.
10 | You may obtain a copy of the License at
11 |
12 | http://www.apache.org/licenses/LICENSE-2.0
13 |
14 | Unless required by applicable law or agreed to in writing, software
15 | distributed under the License is distributed on an "AS IS" BASIS,
16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | See the License for the specific language governing permissions and
18 | limitations under the License.
19 | """
20 |
21 | """Functions for diff, match and patch.
22 |
23 | Computes the difference between two texts to create a patch.
24 | Applies the patch onto another text, allowing for errors.
25 | """
26 |
27 | __author__ = 'fraser@google.com (Neil Fraser)'
28 |
29 | import math
30 | import re
31 | import sys
32 | import time
33 | import urllib.parse
34 |
35 | class diff_match_patch:
36 | """Class containing the diff, match and patch methods.
37 |
38 | Also contains the behaviour settings.
39 | """
40 |
41 | def __init__(self):
42 | """Inits a diff_match_patch object with default settings.
43 | Redefine these in your program to override the defaults.
44 | """
45 |
46 | # Number of seconds to map a diff before giving up (0 for infinity).
47 | self.Diff_Timeout = 1.0
48 | # Cost of an empty edit operation in terms of edit characters.
49 | self.Diff_EditCost = 4
50 | # At what point is no match declared (0.0 = perfection, 1.0 = very loose).
51 | self.Match_Threshold = 0.5
52 | # How far to search for a match (0 = exact location, 1000+ = broad match).
53 | # A match this many characters away from the expected location will add
54 | # 1.0 to the score (0.0 is a perfect match).
55 | self.Match_Distance = 1000
56 | # When deleting a large block of text (over ~64 characters), how close do
57 | # the contents have to be to match the expected contents. (0.0 = perfection,
58 | # 1.0 = very loose). Note that Match_Threshold controls how closely the
59 | # end points of a delete need to match.
60 | self.Patch_DeleteThreshold = 0.5
61 | # Chunk size for context length.
62 | self.Patch_Margin = 4
63 |
64 | # The number of bits in an int.
65 | # Python has no maximum, thus to disable patch splitting set to 0.
66 | # However to avoid long patches in certain pathological cases, use 32.
67 | # Multiple short patches (using native ints) are much faster than long ones.
68 | self.Match_MaxBits = 32
69 |
70 | # DIFF FUNCTIONS
71 |
72 | # The data structure representing a diff is an array of tuples:
73 | # [(DIFF_DELETE, "Hello"), (DIFF_INSERT, "Goodbye"), (DIFF_EQUAL, " world.")]
74 | # which means: delete "Hello", add "Goodbye" and keep " world."
75 | DIFF_DELETE = -1
76 | DIFF_INSERT = 1
77 | DIFF_EQUAL = 0
78 |
79 | def diff_main(self, text1, text2, checklines=True, deadline=None):
80 | """Find the differences between two texts. Simplifies the problem by
81 | stripping any common prefix or suffix off the texts before diffing.
82 |
83 | Args:
84 | text1: Old string to be diffed.
85 | text2: New string to be diffed.
86 | checklines: Optional speedup flag. If present and false, then don't run
87 | a line-level diff first to identify the changed areas.
88 | Defaults to true, which does a faster, slightly less optimal diff.
89 | deadline: Optional time when the diff should be complete by. Used
90 | internally for recursive calls. Users should set DiffTimeout instead.
91 |
92 | Returns:
93 | Array of changes.
94 | """
95 | # Set a deadline by which time the diff must be complete.
96 | if deadline == None:
97 | # Unlike in most languages, Python counts time in seconds.
98 | if self.Diff_Timeout <= 0:
99 | deadline = sys.maxsize
100 | else:
101 | deadline = time.time() + self.Diff_Timeout
102 |
103 | # Check for null inputs.
104 | if text1 == None or text2 == None:
105 | raise ValueError("Null inputs. (diff_main)")
106 |
107 | # Check for equality (speedup).
108 | if text1 == text2:
109 | if text1:
110 | return [(self.DIFF_EQUAL, text1)]
111 | return []
112 |
113 | # Trim off common prefix (speedup).
114 | commonlength = self.diff_commonPrefix(text1, text2)
115 | commonprefix = text1[:commonlength]
116 | text1 = text1[commonlength:]
117 | text2 = text2[commonlength:]
118 |
119 | # Trim off common suffix (speedup).
120 | commonlength = self.diff_commonSuffix(text1, text2)
121 | if commonlength == 0:
122 | commonsuffix = ''
123 | else:
124 | commonsuffix = text1[-commonlength:]
125 | text1 = text1[:-commonlength]
126 | text2 = text2[:-commonlength]
127 |
128 | # Compute the diff on the middle block.
129 | diffs = self.diff_compute(text1, text2, checklines, deadline)
130 |
131 | # Restore the prefix and suffix.
132 | if commonprefix:
133 | diffs[:0] = [(self.DIFF_EQUAL, commonprefix)]
134 | if commonsuffix:
135 | diffs.append((self.DIFF_EQUAL, commonsuffix))
136 | self.diff_cleanupMerge(diffs)
137 | return diffs
138 |
139 | def diff_compute(self, text1, text2, checklines, deadline):
140 | """Find the differences between two texts. Assumes that the texts do not
141 | have any common prefix or suffix.
142 |
143 | Args:
144 | text1: Old string to be diffed.
145 | text2: New string to be diffed.
146 | checklines: Speedup flag. If false, then don't run a line-level diff
147 | first to identify the changed areas.
148 | If true, then run a faster, slightly less optimal diff.
149 | deadline: Time when the diff should be complete by.
150 |
151 | Returns:
152 | Array of changes.
153 | """
154 | if not text1:
155 | # Just add some text (speedup).
156 | return [(self.DIFF_INSERT, text2)]
157 |
158 | if not text2:
159 | # Just delete some text (speedup).
160 | return [(self.DIFF_DELETE, text1)]
161 |
162 | if len(text1) > len(text2):
163 | (longtext, shorttext) = (text1, text2)
164 | else:
165 | (shorttext, longtext) = (text1, text2)
166 | i = longtext.find(shorttext)
167 | if i != -1:
168 | # Shorter text is inside the longer text (speedup).
169 | diffs = [(self.DIFF_INSERT, longtext[:i]), (self.DIFF_EQUAL, shorttext),
170 | (self.DIFF_INSERT, longtext[i + len(shorttext):])]
171 | # Swap insertions for deletions if diff is reversed.
172 | if len(text1) > len(text2):
173 | diffs[0] = (self.DIFF_DELETE, diffs[0][1])
174 | diffs[2] = (self.DIFF_DELETE, diffs[2][1])
175 | return diffs
176 |
177 | if len(shorttext) == 1:
178 | # Single character string.
179 | # After the previous speedup, the character can't be an equality.
180 | return [(self.DIFF_DELETE, text1), (self.DIFF_INSERT, text2)]
181 |
182 | # Check to see if the problem can be split in two.
183 | hm = self.diff_halfMatch(text1, text2)
184 | if hm:
185 | # A half-match was found, sort out the return data.
186 | (text1_a, text1_b, text2_a, text2_b, mid_common) = hm
187 | # Send both pairs off for separate processing.
188 | diffs_a = self.diff_main(text1_a, text2_a, checklines, deadline)
189 | diffs_b = self.diff_main(text1_b, text2_b, checklines, deadline)
190 | # Merge the results.
191 | return diffs_a + [(self.DIFF_EQUAL, mid_common)] + diffs_b
192 |
193 | if checklines and len(text1) > 100 and len(text2) > 100:
194 | return self.diff_lineMode(text1, text2, deadline)
195 |
196 | return self.diff_bisect(text1, text2, deadline)
197 |
198 | def diff_lineMode(self, text1, text2, deadline):
199 | """Do a quick line-level diff on both strings, then rediff the parts for
200 | greater accuracy.
201 | This speedup can produce non-minimal diffs.
202 |
203 | Args:
204 | text1: Old string to be diffed.
205 | text2: New string to be diffed.
206 | deadline: Time when the diff should be complete by.
207 |
208 | Returns:
209 | Array of changes.
210 | """
211 |
212 | # Scan the text on a line-by-line basis first.
213 | (text1, text2, linearray) = self.diff_linesToChars(text1, text2)
214 |
215 | diffs = self.diff_main(text1, text2, False, deadline)
216 |
217 | # Convert the diff back to original text.
218 | self.diff_charsToLines(diffs, linearray)
219 | # Eliminate freak matches (e.g. blank lines)
220 | self.diff_cleanupSemantic(diffs)
221 |
222 | # Rediff any replacement blocks, this time character-by-character.
223 | # Add a dummy entry at the end.
224 | diffs.append((self.DIFF_EQUAL, ''))
225 | pointer = 0
226 | count_delete = 0
227 | count_insert = 0
228 | text_delete = ''
229 | text_insert = ''
230 | while pointer < len(diffs):
231 | if diffs[pointer][0] == self.DIFF_INSERT:
232 | count_insert += 1
233 | text_insert += diffs[pointer][1]
234 | elif diffs[pointer][0] == self.DIFF_DELETE:
235 | count_delete += 1
236 | text_delete += diffs[pointer][1]
237 | elif diffs[pointer][0] == self.DIFF_EQUAL:
238 | # Upon reaching an equality, check for prior redundancies.
239 | if count_delete >= 1 and count_insert >= 1:
240 | # Delete the offending records and add the merged ones.
241 | a = self.diff_main(text_delete, text_insert, False, deadline)
242 | diffs[pointer - count_delete - count_insert : pointer] = a
243 | pointer = pointer - count_delete - count_insert + len(a)
244 | count_insert = 0
245 | count_delete = 0
246 | text_delete = ''
247 | text_insert = ''
248 |
249 | pointer += 1
250 |
251 | diffs.pop() # Remove the dummy entry at the end.
252 |
253 | return diffs
254 |
255 | def diff_bisect(self, text1, text2, deadline):
256 | """Find the 'middle snake' of a diff, split the problem in two
257 | and return the recursively constructed diff.
258 | See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
259 |
260 | Args:
261 | text1: Old string to be diffed.
262 | text2: New string to be diffed.
263 | deadline: Time at which to bail if not yet complete.
264 |
265 | Returns:
266 | Array of diff tuples.
267 | """
268 |
269 | # Cache the text lengths to prevent multiple calls.
270 | text1_length = len(text1)
271 | text2_length = len(text2)
272 | max_d = (text1_length + text2_length + 1) // 2
273 | v_offset = max_d
274 | v_length = 2 * max_d
275 | v1 = [-1] * v_length
276 | v1[v_offset + 1] = 0
277 | v2 = v1[:]
278 | delta = text1_length - text2_length
279 | # If the total number of characters is odd, then the front path will
280 | # collide with the reverse path.
281 | front = (delta % 2 != 0)
282 | # Offsets for start and end of k loop.
283 | # Prevents mapping of space beyond the grid.
284 | k1start = 0
285 | k1end = 0
286 | k2start = 0
287 | k2end = 0
288 | for d in range(max_d):
289 | # Bail out if deadline is reached.
290 | if time.time() > deadline:
291 | break
292 |
293 | # Walk the front path one step.
294 | for k1 in range(-d + k1start, d + 1 - k1end, 2):
295 | k1_offset = v_offset + k1
296 | if k1 == -d or (k1 != d and
297 | v1[k1_offset - 1] < v1[k1_offset + 1]):
298 | x1 = v1[k1_offset + 1]
299 | else:
300 | x1 = v1[k1_offset - 1] + 1
301 | y1 = x1 - k1
302 | while (x1 < text1_length and y1 < text2_length and
303 | text1[x1] == text2[y1]):
304 | x1 += 1
305 | y1 += 1
306 | v1[k1_offset] = x1
307 | if x1 > text1_length:
308 | # Ran off the right of the graph.
309 | k1end += 2
310 | elif y1 > text2_length:
311 | # Ran off the bottom of the graph.
312 | k1start += 2
313 | elif front:
314 | k2_offset = v_offset + delta - k1
315 | if k2_offset >= 0 and k2_offset < v_length and v2[k2_offset] != -1:
316 | # Mirror x2 onto top-left coordinate system.
317 | x2 = text1_length - v2[k2_offset]
318 | if x1 >= x2:
319 | # Overlap detected.
320 | return self.diff_bisectSplit(text1, text2, x1, y1, deadline)
321 |
322 | # Walk the reverse path one step.
323 | for k2 in range(-d + k2start, d + 1 - k2end, 2):
324 | k2_offset = v_offset + k2
325 | if k2 == -d or (k2 != d and
326 | v2[k2_offset - 1] < v2[k2_offset + 1]):
327 | x2 = v2[k2_offset + 1]
328 | else:
329 | x2 = v2[k2_offset - 1] + 1
330 | y2 = x2 - k2
331 | while (x2 < text1_length and y2 < text2_length and
332 | text1[-x2 - 1] == text2[-y2 - 1]):
333 | x2 += 1
334 | y2 += 1
335 | v2[k2_offset] = x2
336 | if x2 > text1_length:
337 | # Ran off the left of the graph.
338 | k2end += 2
339 | elif y2 > text2_length:
340 | # Ran off the top of the graph.
341 | k2start += 2
342 | elif not front:
343 | k1_offset = v_offset + delta - k2
344 | if k1_offset >= 0 and k1_offset < v_length and v1[k1_offset] != -1:
345 | x1 = v1[k1_offset]
346 | y1 = v_offset + x1 - k1_offset
347 | # Mirror x2 onto top-left coordinate system.
348 | x2 = text1_length - x2
349 | if x1 >= x2:
350 | # Overlap detected.
351 | return self.diff_bisectSplit(text1, text2, x1, y1, deadline)
352 |
353 | # Diff took too long and hit the deadline or
354 | # number of diffs equals number of characters, no commonality at all.
355 | return [(self.DIFF_DELETE, text1), (self.DIFF_INSERT, text2)]
356 |
357 | def diff_bisectSplit(self, text1, text2, x, y, deadline):
358 | """Given the location of the 'middle snake', split the diff in two parts
359 | and recurse.
360 |
361 | Args:
362 | text1: Old string to be diffed.
363 | text2: New string to be diffed.
364 | x: Index of split point in text1.
365 | y: Index of split point in text2.
366 | deadline: Time at which to bail if not yet complete.
367 |
368 | Returns:
369 | Array of diff tuples.
370 | """
371 | text1a = text1[:x]
372 | text2a = text2[:y]
373 | text1b = text1[x:]
374 | text2b = text2[y:]
375 |
376 | # Compute both diffs serially.
377 | diffs = self.diff_main(text1a, text2a, False, deadline)
378 | diffsb = self.diff_main(text1b, text2b, False, deadline)
379 |
380 | return diffs + diffsb
381 |
382 | def diff_linesToChars(self, text1, text2):
383 | """Split two texts into an array of strings. Reduce the texts to a string
384 | of hashes where each Unicode character represents one line.
385 |
386 | Args:
387 | text1: First string.
388 | text2: Second string.
389 |
390 | Returns:
391 | Three element tuple, containing the encoded text1, the encoded text2 and
392 | the array of unique strings. The zeroth element of the array of unique
393 | strings is intentionally blank.
394 | """
395 | lineArray = [] # e.g. lineArray[4] == "Hello\n"
396 | lineHash = {} # e.g. lineHash["Hello\n"] == 4
397 |
398 | # "\x00" is a valid character, but various debuggers don't like it.
399 | # So we'll insert a junk entry to avoid generating a null character.
400 | lineArray.append('')
401 |
402 | def diff_linesToCharsMunge(text):
403 | """Split a text into an array of strings. Reduce the texts to a string
404 | of hashes where each Unicode character represents one line.
405 | Modifies linearray and linehash through being a closure.
406 |
407 | Args:
408 | text: String to encode.
409 |
410 | Returns:
411 | Encoded string.
412 | """
413 | chars = []
414 | # Walk the text, pulling out a substring for each line.
415 | # text.split('\n') would would temporarily double our memory footprint.
416 | # Modifying text would create many large strings to garbage collect.
417 | lineStart = 0
418 | lineEnd = -1
419 | while lineEnd < len(text) - 1:
420 | lineEnd = text.find('\n', lineStart)
421 | if lineEnd == -1:
422 | lineEnd = len(text) - 1
423 | line = text[lineStart:lineEnd + 1]
424 | lineStart = lineEnd + 1
425 |
426 | if line in lineHash:
427 | chars.append(chr(lineHash[line]))
428 | else:
429 | lineArray.append(line)
430 | lineHash[line] = len(lineArray) - 1
431 | chars.append(chr(len(lineArray) - 1))
432 | return "".join(chars)
433 |
434 | chars1 = diff_linesToCharsMunge(text1)
435 | chars2 = diff_linesToCharsMunge(text2)
436 | return (chars1, chars2, lineArray)
437 |
438 | def diff_charsToLines(self, diffs, lineArray):
439 | """Rehydrate the text in a diff from a string of line hashes to real lines
440 | of text.
441 |
442 | Args:
443 | diffs: Array of diff tuples.
444 | lineArray: Array of unique strings.
445 | """
446 | for x in range(len(diffs)):
447 | text = []
448 | for char in diffs[x][1]:
449 | text.append(lineArray[ord(char)])
450 | diffs[x] = (diffs[x][0], "".join(text))
451 |
452 | def diff_commonPrefix(self, text1, text2):
453 | """Determine the common prefix of two strings.
454 |
455 | Args:
456 | text1: First string.
457 | text2: Second string.
458 |
459 | Returns:
460 | The number of characters common to the start of each string.
461 | """
462 | # Quick check for common null cases.
463 | if not text1 or not text2 or text1[0] != text2[0]:
464 | return 0
465 | # Binary search.
466 | # Performance analysis: http://neil.fraser.name/news/2007/10/09/
467 | pointermin = 0
468 | pointermax = min(len(text1), len(text2))
469 | pointermid = pointermax
470 | pointerstart = 0
471 | while pointermin < pointermid:
472 | if text1[pointerstart:pointermid] == text2[pointerstart:pointermid]:
473 | pointermin = pointermid
474 | pointerstart = pointermin
475 | else:
476 | pointermax = pointermid
477 | pointermid = (pointermax - pointermin) // 2 + pointermin
478 | return pointermid
479 |
480 | def diff_commonSuffix(self, text1, text2):
481 | """Determine the common suffix of two strings.
482 |
483 | Args:
484 | text1: First string.
485 | text2: Second string.
486 |
487 | Returns:
488 | The number of characters common to the end of each string.
489 | """
490 | # Quick check for common null cases.
491 | if not text1 or not text2 or text1[-1] != text2[-1]:
492 | return 0
493 | # Binary search.
494 | # Performance analysis: http://neil.fraser.name/news/2007/10/09/
495 | pointermin = 0
496 | pointermax = min(len(text1), len(text2))
497 | pointermid = pointermax
498 | pointerend = 0
499 | while pointermin < pointermid:
500 | if (text1[-pointermid:len(text1) - pointerend] ==
501 | text2[-pointermid:len(text2) - pointerend]):
502 | pointermin = pointermid
503 | pointerend = pointermin
504 | else:
505 | pointermax = pointermid
506 | pointermid = (pointermax - pointermin) // 2 + pointermin
507 | return pointermid
508 |
509 | def diff_commonOverlap(self, text1, text2):
510 | """Determine if the suffix of one string is the prefix of another.
511 |
512 | Args:
513 | text1 First string.
514 | text2 Second string.
515 |
516 | Returns:
517 | The number of characters common to the end of the first
518 | string and the start of the second string.
519 | """
520 | # Cache the text lengths to prevent multiple calls.
521 | text1_length = len(text1)
522 | text2_length = len(text2)
523 | # Eliminate the null case.
524 | if text1_length == 0 or text2_length == 0:
525 | return 0
526 | # Truncate the longer string.
527 | if text1_length > text2_length:
528 | text1 = text1[-text2_length:]
529 | elif text1_length < text2_length:
530 | text2 = text2[:text1_length]
531 | text_length = min(text1_length, text2_length)
532 | # Quick check for the worst case.
533 | if text1 == text2:
534 | return text_length
535 |
536 | # Start by looking for a single character match
537 | # and increase length until no match is found.
538 | # Performance analysis: http://neil.fraser.name/news/2010/11/04/
539 | best = 0
540 | length = 1
541 | while True:
542 | pattern = text1[-length:]
543 | found = text2.find(pattern)
544 | if found == -1:
545 | return best
546 | length += found
547 | if found == 0 or text1[-length:] == text2[:length]:
548 | best = length
549 | length += 1
550 |
551 | def diff_halfMatch(self, text1, text2):
552 | """Do the two texts share a substring which is at least half the length of
553 | the longer text?
554 | This speedup can produce non-minimal diffs.
555 |
556 | Args:
557 | text1: First string.
558 | text2: Second string.
559 |
560 | Returns:
561 | Five element Array, containing the prefix of text1, the suffix of text1,
562 | the prefix of text2, the suffix of text2 and the common middle. Or None
563 | if there was no match.
564 | """
565 | if self.Diff_Timeout <= 0:
566 | # Don't risk returning a non-optimal diff if we have unlimited time.
567 | return None
568 | if len(text1) > len(text2):
569 | (longtext, shorttext) = (text1, text2)
570 | else:
571 | (shorttext, longtext) = (text1, text2)
572 | if len(longtext) < 4 or len(shorttext) * 2 < len(longtext):
573 | return None # Pointless.
574 |
575 | def diff_halfMatchI(longtext, shorttext, i):
576 | """Does a substring of shorttext exist within longtext such that the
577 | substring is at least half the length of longtext?
578 | Closure, but does not reference any external variables.
579 |
580 | Args:
581 | longtext: Longer string.
582 | shorttext: Shorter string.
583 | i: Start index of quarter length substring within longtext.
584 |
585 | Returns:
586 | Five element Array, containing the prefix of longtext, the suffix of
587 | longtext, the prefix of shorttext, the suffix of shorttext and the
588 | common middle. Or None if there was no match.
589 | """
590 | seed = longtext[i:i + len(longtext) // 4]
591 | best_common = ''
592 | j = shorttext.find(seed)
593 | while j != -1:
594 | prefixLength = self.diff_commonPrefix(longtext[i:], shorttext[j:])
595 | suffixLength = self.diff_commonSuffix(longtext[:i], shorttext[:j])
596 | if len(best_common) < suffixLength + prefixLength:
597 | best_common = (shorttext[j - suffixLength:j] +
598 | shorttext[j:j + prefixLength])
599 | best_longtext_a = longtext[:i - suffixLength]
600 | best_longtext_b = longtext[i + prefixLength:]
601 | best_shorttext_a = shorttext[:j - suffixLength]
602 | best_shorttext_b = shorttext[j + prefixLength:]
603 | j = shorttext.find(seed, j + 1)
604 |
605 | if len(best_common) * 2 >= len(longtext):
606 | return (best_longtext_a, best_longtext_b,
607 | best_shorttext_a, best_shorttext_b, best_common)
608 | else:
609 | return None
610 |
611 | # First check if the second quarter is the seed for a half-match.
612 | hm1 = diff_halfMatchI(longtext, shorttext, (len(longtext) + 3) // 4)
613 | # Check again based on the third quarter.
614 | hm2 = diff_halfMatchI(longtext, shorttext, (len(longtext) + 1) // 2)
615 | if not hm1 and not hm2:
616 | return None
617 | elif not hm2:
618 | hm = hm1
619 | elif not hm1:
620 | hm = hm2
621 | else:
622 | # Both matched. Select the longest.
623 | if len(hm1[4]) > len(hm2[4]):
624 | hm = hm1
625 | else:
626 | hm = hm2
627 |
628 | # A half-match was found, sort out the return data.
629 | if len(text1) > len(text2):
630 | (text1_a, text1_b, text2_a, text2_b, mid_common) = hm
631 | else:
632 | (text2_a, text2_b, text1_a, text1_b, mid_common) = hm
633 | return (text1_a, text1_b, text2_a, text2_b, mid_common)
634 |
635 | def diff_cleanupSemantic(self, diffs):
636 | """Reduce the number of edits by eliminating semantically trivial
637 | equalities.
638 |
639 | Args:
640 | diffs: Array of diff tuples.
641 | """
642 | changes = False
643 | equalities = [] # Stack of indices where equalities are found.
644 | lastequality = None # Always equal to diffs[equalities[-1]][1]
645 | pointer = 0 # Index of current position.
646 | # Number of chars that changed prior to the equality.
647 | length_insertions1, length_deletions1 = 0, 0
648 | # Number of chars that changed after the equality.
649 | length_insertions2, length_deletions2 = 0, 0
650 | while pointer < len(diffs):
651 | if diffs[pointer][0] == self.DIFF_EQUAL: # Equality found.
652 | equalities.append(pointer)
653 | length_insertions1, length_insertions2 = length_insertions2, 0
654 | length_deletions1, length_deletions2 = length_deletions2, 0
655 | lastequality = diffs[pointer][1]
656 | else: # An insertion or deletion.
657 | if diffs[pointer][0] == self.DIFF_INSERT:
658 | length_insertions2 += len(diffs[pointer][1])
659 | else:
660 | length_deletions2 += len(diffs[pointer][1])
661 | # Eliminate an equality that is smaller or equal to the edits on both
662 | # sides of it.
663 | if (lastequality and (len(lastequality) <=
664 | max(length_insertions1, length_deletions1)) and
665 | (len(lastequality) <= max(length_insertions2, length_deletions2))):
666 | # Duplicate record.
667 | diffs.insert(equalities[-1], (self.DIFF_DELETE, lastequality))
668 | # Change second copy to insert.
669 | diffs[equalities[-1] + 1] = (self.DIFF_INSERT,
670 | diffs[equalities[-1] + 1][1])
671 | # Throw away the equality we just deleted.
672 | equalities.pop()
673 | # Throw away the previous equality (it needs to be reevaluated).
674 | if len(equalities):
675 | equalities.pop()
676 | if len(equalities):
677 | pointer = equalities[-1]
678 | else:
679 | pointer = -1
680 | # Reset the counters.
681 | length_insertions1, length_deletions1 = 0, 0
682 | length_insertions2, length_deletions2 = 0, 0
683 | lastequality = None
684 | changes = True
685 | pointer += 1
686 |
687 | # Normalize the diff.
688 | if changes:
689 | self.diff_cleanupMerge(diffs)
690 | self.diff_cleanupSemanticLossless(diffs)
691 |
692 | # Find any overlaps between deletions and insertions.
693 | # e.g: abcxxxxxxdef
694 | # -> abcxxxdef
695 | # e.g: xxxabcdefxxx
696 | # -> defxxxabc
697 | # Only extract an overlap if it is as big as the edit ahead or behind it.
698 | pointer = 1
699 | while pointer < len(diffs):
700 | if (diffs[pointer - 1][0] == self.DIFF_DELETE and
701 | diffs[pointer][0] == self.DIFF_INSERT):
702 | deletion = diffs[pointer - 1][1]
703 | insertion = diffs[pointer][1]
704 | overlap_length1 = self.diff_commonOverlap(deletion, insertion)
705 | overlap_length2 = self.diff_commonOverlap(insertion, deletion)
706 | if overlap_length1 >= overlap_length2:
707 | if (overlap_length1 >= len(deletion) / 2.0 or
708 | overlap_length1 >= len(insertion) / 2.0):
709 | # Overlap found. Insert an equality and trim the surrounding edits.
710 | diffs.insert(pointer, (self.DIFF_EQUAL,
711 | insertion[:overlap_length1]))
712 | diffs[pointer - 1] = (self.DIFF_DELETE,
713 | deletion[:len(deletion) - overlap_length1])
714 | diffs[pointer + 1] = (self.DIFF_INSERT,
715 | insertion[overlap_length1:])
716 | pointer += 1
717 | else:
718 | if (overlap_length2 >= len(deletion) / 2.0 or
719 | overlap_length2 >= len(insertion) / 2.0):
720 | # Reverse overlap found.
721 | # Insert an equality and swap and trim the surrounding edits.
722 | diffs.insert(pointer, (self.DIFF_EQUAL, deletion[:overlap_length2]))
723 | diffs[pointer - 1] = (self.DIFF_INSERT,
724 | insertion[:len(insertion) - overlap_length2])
725 | diffs[pointer + 1] = (self.DIFF_DELETE, deletion[overlap_length2:])
726 | pointer += 1
727 | pointer += 1
728 | pointer += 1
729 |
730 | def diff_cleanupSemanticLossless(self, diffs):
731 | """Look for single edits surrounded on both sides by equalities
732 | which can be shifted sideways to align the edit to a word boundary.
733 | e.g: The cat came. -> The cat came.
734 |
735 | Args:
736 | diffs: Array of diff tuples.
737 | """
738 |
739 | def diff_cleanupSemanticScore(one, two):
740 | """Given two strings, compute a score representing whether the
741 | internal boundary falls on logical boundaries.
742 | Scores range from 6 (best) to 0 (worst).
743 | Closure, but does not reference any external variables.
744 |
745 | Args:
746 | one: First string.
747 | two: Second string.
748 |
749 | Returns:
750 | The score.
751 | """
752 | if not one or not two:
753 | # Edges are the best.
754 | return 6
755 |
756 | # Each port of this function behaves slightly differently due to
757 | # subtle differences in each language's definition of things like
758 | # 'whitespace'. Since this function's purpose is largely cosmetic,
759 | # the choice has been made to use each language's native features
760 | # rather than force total conformity.
761 | char1 = one[-1]
762 | char2 = two[0]
763 | nonAlphaNumeric1 = not char1.isalnum()
764 | nonAlphaNumeric2 = not char2.isalnum()
765 | whitespace1 = nonAlphaNumeric1 and char1.isspace()
766 | whitespace2 = nonAlphaNumeric2 and char2.isspace()
767 | lineBreak1 = whitespace1 and (char1 == "\r" or char1 == "\n")
768 | lineBreak2 = whitespace2 and (char2 == "\r" or char2 == "\n")
769 | blankLine1 = lineBreak1 and self.BLANKLINEEND.search(one)
770 | blankLine2 = lineBreak2 and self.BLANKLINESTART.match(two)
771 |
772 | if blankLine1 or blankLine2:
773 | # Five points for blank lines.
774 | return 5
775 | elif lineBreak1 or lineBreak2:
776 | # Four points for line breaks.
777 | return 4
778 | elif nonAlphaNumeric1 and not whitespace1 and whitespace2:
779 | # Three points for end of sentences.
780 | return 3
781 | elif whitespace1 or whitespace2:
782 | # Two points for whitespace.
783 | return 2
784 | elif nonAlphaNumeric1 or nonAlphaNumeric2:
785 | # One point for non-alphanumeric.
786 | return 1
787 | return 0
788 |
789 | pointer = 1
790 | # Intentionally ignore the first and last element (don't need checking).
791 | while pointer < len(diffs) - 1:
792 | if (diffs[pointer - 1][0] == self.DIFF_EQUAL and
793 | diffs[pointer + 1][0] == self.DIFF_EQUAL):
794 | # This is a single edit surrounded by equalities.
795 | equality1 = diffs[pointer - 1][1]
796 | edit = diffs[pointer][1]
797 | equality2 = diffs[pointer + 1][1]
798 |
799 | # First, shift the edit as far left as possible.
800 | commonOffset = self.diff_commonSuffix(equality1, edit)
801 | if commonOffset:
802 | commonString = edit[-commonOffset:]
803 | equality1 = equality1[:-commonOffset]
804 | edit = commonString + edit[:-commonOffset]
805 | equality2 = commonString + equality2
806 |
807 | # Second, step character by character right, looking for the best fit.
808 | bestEquality1 = equality1
809 | bestEdit = edit
810 | bestEquality2 = equality2
811 | bestScore = (diff_cleanupSemanticScore(equality1, edit) +
812 | diff_cleanupSemanticScore(edit, equality2))
813 | while edit and equality2 and edit[0] == equality2[0]:
814 | equality1 += edit[0]
815 | edit = edit[1:] + equality2[0]
816 | equality2 = equality2[1:]
817 | score = (diff_cleanupSemanticScore(equality1, edit) +
818 | diff_cleanupSemanticScore(edit, equality2))
819 | # The >= encourages trailing rather than leading whitespace on edits.
820 | if score >= bestScore:
821 | bestScore = score
822 | bestEquality1 = equality1
823 | bestEdit = edit
824 | bestEquality2 = equality2
825 |
826 | if diffs[pointer - 1][1] != bestEquality1:
827 | # We have an improvement, save it back to the diff.
828 | if bestEquality1:
829 | diffs[pointer - 1] = (diffs[pointer - 1][0], bestEquality1)
830 | else:
831 | del diffs[pointer - 1]
832 | pointer -= 1
833 | diffs[pointer] = (diffs[pointer][0], bestEdit)
834 | if bestEquality2:
835 | diffs[pointer + 1] = (diffs[pointer + 1][0], bestEquality2)
836 | else:
837 | del diffs[pointer + 1]
838 | pointer -= 1
839 | pointer += 1
840 |
841 | # Define some regex patterns for matching boundaries.
842 | BLANKLINEEND = re.compile(r"\n\r?\n$");
843 | BLANKLINESTART = re.compile(r"^\r?\n\r?\n");
844 |
845 | def diff_cleanupEfficiency(self, diffs):
846 | """Reduce the number of edits by eliminating operationally trivial
847 | equalities.
848 |
849 | Args:
850 | diffs: Array of diff tuples.
851 | """
852 | changes = False
853 | equalities = [] # Stack of indices where equalities are found.
854 | lastequality = None # Always equal to diffs[equalities[-1]][1]
855 | pointer = 0 # Index of current position.
856 | pre_ins = False # Is there an insertion operation before the last equality.
857 | pre_del = False # Is there a deletion operation before the last equality.
858 | post_ins = False # Is there an insertion operation after the last equality.
859 | post_del = False # Is there a deletion operation after the last equality.
860 | while pointer < len(diffs):
861 | if diffs[pointer][0] == self.DIFF_EQUAL: # Equality found.
862 | if (len(diffs[pointer][1]) < self.Diff_EditCost and
863 | (post_ins or post_del)):
864 | # Candidate found.
865 | equalities.append(pointer)
866 | pre_ins = post_ins
867 | pre_del = post_del
868 | lastequality = diffs[pointer][1]
869 | else:
870 | # Not a candidate, and can never become one.
871 | equalities = []
872 | lastequality = None
873 |
874 | post_ins = post_del = False
875 | else: # An insertion or deletion.
876 | if diffs[pointer][0] == self.DIFF_DELETE:
877 | post_del = True
878 | else:
879 | post_ins = True
880 |
881 | # Five types to be split:
882 | # ABXYCD
883 | # AXCD
884 | # ABXC
885 | # AXCD
886 | # ABXC
887 |
888 | if lastequality and ((pre_ins and pre_del and post_ins and post_del) or
889 | ((len(lastequality) < self.Diff_EditCost / 2) and
890 | (pre_ins + pre_del + post_ins + post_del) == 3)):
891 | # Duplicate record.
892 | diffs.insert(equalities[-1], (self.DIFF_DELETE, lastequality))
893 | # Change second copy to insert.
894 | diffs[equalities[-1] + 1] = (self.DIFF_INSERT,
895 | diffs[equalities[-1] + 1][1])
896 | equalities.pop() # Throw away the equality we just deleted.
897 | lastequality = None
898 | if pre_ins and pre_del:
899 | # No changes made which could affect previous entry, keep going.
900 | post_ins = post_del = True
901 | equalities = []
902 | else:
903 | if len(equalities):
904 | equalities.pop() # Throw away the previous equality.
905 | if len(equalities):
906 | pointer = equalities[-1]
907 | else:
908 | pointer = -1
909 | post_ins = post_del = False
910 | changes = True
911 | pointer += 1
912 |
913 | if changes:
914 | self.diff_cleanupMerge(diffs)
915 |
916 | def diff_cleanupMerge(self, diffs):
917 | """Reorder and merge like edit sections. Merge equalities.
918 | Any edit section can move as long as it doesn't cross an equality.
919 |
920 | Args:
921 | diffs: Array of diff tuples.
922 | """
923 | diffs.append((self.DIFF_EQUAL, '')) # Add a dummy entry at the end.
924 | pointer = 0
925 | count_delete = 0
926 | count_insert = 0
927 | text_delete = ''
928 | text_insert = ''
929 | while pointer < len(diffs):
930 | if diffs[pointer][0] == self.DIFF_INSERT:
931 | count_insert += 1
932 | text_insert += diffs[pointer][1]
933 | pointer += 1
934 | elif diffs[pointer][0] == self.DIFF_DELETE:
935 | count_delete += 1
936 | text_delete += diffs[pointer][1]
937 | pointer += 1
938 | elif diffs[pointer][0] == self.DIFF_EQUAL:
939 | # Upon reaching an equality, check for prior redundancies.
940 | if count_delete + count_insert > 1:
941 | if count_delete != 0 and count_insert != 0:
942 | # Factor out any common prefixies.
943 | commonlength = self.diff_commonPrefix(text_insert, text_delete)
944 | if commonlength != 0:
945 | x = pointer - count_delete - count_insert - 1
946 | if x >= 0 and diffs[x][0] == self.DIFF_EQUAL:
947 | diffs[x] = (diffs[x][0], diffs[x][1] +
948 | text_insert[:commonlength])
949 | else:
950 | diffs.insert(0, (self.DIFF_EQUAL, text_insert[:commonlength]))
951 | pointer += 1
952 | text_insert = text_insert[commonlength:]
953 | text_delete = text_delete[commonlength:]
954 | # Factor out any common suffixies.
955 | commonlength = self.diff_commonSuffix(text_insert, text_delete)
956 | if commonlength != 0:
957 | diffs[pointer] = (diffs[pointer][0], text_insert[-commonlength:] +
958 | diffs[pointer][1])
959 | text_insert = text_insert[:-commonlength]
960 | text_delete = text_delete[:-commonlength]
961 | # Delete the offending records and add the merged ones.
962 | if count_delete == 0:
963 | diffs[pointer - count_insert : pointer] = [
964 | (self.DIFF_INSERT, text_insert)]
965 | elif count_insert == 0:
966 | diffs[pointer - count_delete : pointer] = [
967 | (self.DIFF_DELETE, text_delete)]
968 | else:
969 | diffs[pointer - count_delete - count_insert : pointer] = [
970 | (self.DIFF_DELETE, text_delete),
971 | (self.DIFF_INSERT, text_insert)]
972 | pointer = pointer - count_delete - count_insert + 1
973 | if count_delete != 0:
974 | pointer += 1
975 | if count_insert != 0:
976 | pointer += 1
977 | elif pointer != 0 and diffs[pointer - 1][0] == self.DIFF_EQUAL:
978 | # Merge this equality with the previous one.
979 | diffs[pointer - 1] = (diffs[pointer - 1][0],
980 | diffs[pointer - 1][1] + diffs[pointer][1])
981 | del diffs[pointer]
982 | else:
983 | pointer += 1
984 |
985 | count_insert = 0
986 | count_delete = 0
987 | text_delete = ''
988 | text_insert = ''
989 |
990 | if diffs[-1][1] == '':
991 | diffs.pop() # Remove the dummy entry at the end.
992 |
993 | # Second pass: look for single edits surrounded on both sides by equalities
994 | # which can be shifted sideways to eliminate an equality.
995 | # e.g: ABAC -> ABAC
996 | changes = False
997 | pointer = 1
998 | # Intentionally ignore the first and last element (don't need checking).
999 | while pointer < len(diffs) - 1:
1000 | if (diffs[pointer - 1][0] == self.DIFF_EQUAL and
1001 | diffs[pointer + 1][0] == self.DIFF_EQUAL):
1002 | # This is a single edit surrounded by equalities.
1003 | if diffs[pointer][1].endswith(diffs[pointer - 1][1]):
1004 | # Shift the edit over the previous equality.
1005 | diffs[pointer] = (diffs[pointer][0],
1006 | diffs[pointer - 1][1] +
1007 | diffs[pointer][1][:-len(diffs[pointer - 1][1])])
1008 | diffs[pointer + 1] = (diffs[pointer + 1][0],
1009 | diffs[pointer - 1][1] + diffs[pointer + 1][1])
1010 | del diffs[pointer - 1]
1011 | changes = True
1012 | elif diffs[pointer][1].startswith(diffs[pointer + 1][1]):
1013 | # Shift the edit over the next equality.
1014 | diffs[pointer - 1] = (diffs[pointer - 1][0],
1015 | diffs[pointer - 1][1] + diffs[pointer + 1][1])
1016 | diffs[pointer] = (diffs[pointer][0],
1017 | diffs[pointer][1][len(diffs[pointer + 1][1]):] +
1018 | diffs[pointer + 1][1])
1019 | del diffs[pointer + 1]
1020 | changes = True
1021 | pointer += 1
1022 |
1023 | # If shifts were made, the diff needs reordering and another shift sweep.
1024 | if changes:
1025 | self.diff_cleanupMerge(diffs)
1026 |
1027 | def diff_xIndex(self, diffs, loc):
1028 | """loc is a location in text1, compute and return the equivalent location
1029 | in text2. e.g. "The cat" vs "The big cat", 1->1, 5->8
1030 |
1031 | Args:
1032 | diffs: Array of diff tuples.
1033 | loc: Location within text1.
1034 |
1035 | Returns:
1036 | Location within text2.
1037 | """
1038 | chars1 = 0
1039 | chars2 = 0
1040 | last_chars1 = 0
1041 | last_chars2 = 0
1042 | for x in range(len(diffs)):
1043 | (op, text) = diffs[x]
1044 | if op != self.DIFF_INSERT: # Equality or deletion.
1045 | chars1 += len(text)
1046 | if op != self.DIFF_DELETE: # Equality or insertion.
1047 | chars2 += len(text)
1048 | if chars1 > loc: # Overshot the location.
1049 | break
1050 | last_chars1 = chars1
1051 | last_chars2 = chars2
1052 |
1053 | if len(diffs) != x and diffs[x][0] == self.DIFF_DELETE:
1054 | # The location was deleted.
1055 | return last_chars2
1056 | # Add the remaining len(character).
1057 | return last_chars2 + (loc - last_chars1)
1058 |
1059 | def diff_prettyHtml(self, diffs):
1060 | """Convert a diff array into a pretty HTML report.
1061 |
1062 | Args:
1063 | diffs: Array of diff tuples.
1064 |
1065 | Returns:
1066 | HTML representation.
1067 | """
1068 | html = []
1069 | for (op, data) in diffs:
1070 | text = (data.replace("&", "&").replace("<", "<")
1071 | .replace(">", ">").replace("\n", "¶
"))
1072 | if op == self.DIFF_INSERT:
1073 | html.append("%s" % text)
1074 | elif op == self.DIFF_DELETE:
1075 | html.append("%s" % text)
1076 | elif op == self.DIFF_EQUAL:
1077 | html.append("%s" % text)
1078 | return "".join(html)
1079 |
1080 | def diff_text1(self, diffs):
1081 | """Compute and return the source text (all equalities and deletions).
1082 |
1083 | Args:
1084 | diffs: Array of diff tuples.
1085 |
1086 | Returns:
1087 | Source text.
1088 | """
1089 | text = []
1090 | for (op, data) in diffs:
1091 | if op != self.DIFF_INSERT:
1092 | text.append(data)
1093 | return "".join(text)
1094 |
1095 | def diff_text2(self, diffs):
1096 | """Compute and return the destination text (all equalities and insertions).
1097 |
1098 | Args:
1099 | diffs: Array of diff tuples.
1100 |
1101 | Returns:
1102 | Destination text.
1103 | """
1104 | text = []
1105 | for (op, data) in diffs:
1106 | if op != self.DIFF_DELETE:
1107 | text.append(data)
1108 | return "".join(text)
1109 |
1110 | def diff_levenshtein(self, diffs):
1111 | """Compute the Levenshtein distance; the number of inserted, deleted or
1112 | substituted characters.
1113 |
1114 | Args:
1115 | diffs: Array of diff tuples.
1116 |
1117 | Returns:
1118 | Number of changes.
1119 | """
1120 | levenshtein = 0
1121 | insertions = 0
1122 | deletions = 0
1123 | for (op, data) in diffs:
1124 | if op == self.DIFF_INSERT:
1125 | insertions += len(data)
1126 | elif op == self.DIFF_DELETE:
1127 | deletions += len(data)
1128 | elif op == self.DIFF_EQUAL:
1129 | # A deletion and an insertion is one substitution.
1130 | levenshtein += max(insertions, deletions)
1131 | insertions = 0
1132 | deletions = 0
1133 | levenshtein += max(insertions, deletions)
1134 | return levenshtein
1135 |
1136 | def diff_toDelta(self, diffs):
1137 | """Crush the diff into an encoded string which describes the operations
1138 | required to transform text1 into text2.
1139 | E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'.
1140 | Operations are tab-separated. Inserted text is escaped using %xx notation.
1141 |
1142 | Args:
1143 | diffs: Array of diff tuples.
1144 |
1145 | Returns:
1146 | Delta text.
1147 | """
1148 | text = []
1149 | for (op, data) in diffs:
1150 | if op == self.DIFF_INSERT:
1151 | # High ascii will raise UnicodeDecodeError. Use Unicode instead.
1152 | data = data.encode("utf-8")
1153 | text.append("+" + urllib.parse.quote(data, "!~*'();/?:@&=+$,# "))
1154 | elif op == self.DIFF_DELETE:
1155 | text.append("-%d" % len(data))
1156 | elif op == self.DIFF_EQUAL:
1157 | text.append("=%d" % len(data))
1158 | return "\t".join(text)
1159 |
1160 | def diff_fromDelta(self, text1, delta):
1161 | """Given the original text1, and an encoded string which describes the
1162 | operations required to transform text1 into text2, compute the full diff.
1163 |
1164 | Args:
1165 | text1: Source string for the diff.
1166 | delta: Delta text.
1167 |
1168 | Returns:
1169 | Array of diff tuples.
1170 |
1171 | Raises:
1172 | ValueError: If invalid input.
1173 | """
1174 | diffs = []
1175 | pointer = 0 # Cursor in text1
1176 | tokens = delta.split("\t")
1177 | for token in tokens:
1178 | if token == "":
1179 | # Blank tokens are ok (from a trailing \t).
1180 | continue
1181 | # Each token begins with a one character parameter which specifies the
1182 | # operation of this token (delete, insert, equality).
1183 | param = token[1:]
1184 | if token[0] == "+":
1185 | param = urllib.parse.unquote(param)
1186 | diffs.append((self.DIFF_INSERT, param))
1187 | elif token[0] == "-" or token[0] == "=":
1188 | try:
1189 | n = int(param)
1190 | except ValueError:
1191 | raise ValueError("Invalid number in diff_fromDelta: " + param)
1192 | if n < 0:
1193 | raise ValueError("Negative number in diff_fromDelta: " + param)
1194 | text = text1[pointer : pointer + n]
1195 | pointer += n
1196 | if token[0] == "=":
1197 | diffs.append((self.DIFF_EQUAL, text))
1198 | else:
1199 | diffs.append((self.DIFF_DELETE, text))
1200 | else:
1201 | # Anything else is an error.
1202 | raise ValueError("Invalid diff operation in diff_fromDelta: " +
1203 | token[0])
1204 | if pointer != len(text1):
1205 | raise ValueError(
1206 | "Delta length (%d) does not equal source text length (%d)." %
1207 | (pointer, len(text1)))
1208 | return diffs
1209 |
1210 | # MATCH FUNCTIONS
1211 |
1212 | def match_main(self, text, pattern, loc):
1213 | """Locate the best instance of 'pattern' in 'text' near 'loc'.
1214 |
1215 | Args:
1216 | text: The text to search.
1217 | pattern: The pattern to search for.
1218 | loc: The location to search around.
1219 |
1220 | Returns:
1221 | Best match index or -1.
1222 | """
1223 | # Check for null inputs.
1224 | if text == None or pattern == None:
1225 | raise ValueError("Null inputs. (match_main)")
1226 |
1227 | loc = max(0, min(loc, len(text)))
1228 | if text == pattern:
1229 | # Shortcut (potentially not guaranteed by the algorithm)
1230 | return 0
1231 | elif not text:
1232 | # Nothing to match.
1233 | return -1
1234 | elif text[loc:loc + len(pattern)] == pattern:
1235 | # Perfect match at the perfect spot! (Includes case of null pattern)
1236 | return loc
1237 | else:
1238 | # Do a fuzzy compare.
1239 | match = self.match_bitap(text, pattern, loc)
1240 | return match
1241 |
1242 | def match_bitap(self, text, pattern, loc):
1243 | """Locate the best instance of 'pattern' in 'text' near 'loc' using the
1244 | Bitap algorithm.
1245 |
1246 | Args:
1247 | text: The text to search.
1248 | pattern: The pattern to search for.
1249 | loc: The location to search around.
1250 |
1251 | Returns:
1252 | Best match index or -1.
1253 | """
1254 | # Python doesn't have a maxint limit, so ignore this check.
1255 | #if self.Match_MaxBits != 0 and len(pattern) > self.Match_MaxBits:
1256 | # raise ValueError("Pattern too long for this application.")
1257 |
1258 | # Initialise the alphabet.
1259 | s = self.match_alphabet(pattern)
1260 |
1261 | def match_bitapScore(e, x):
1262 | """Compute and return the score for a match with e errors and x location.
1263 | Accesses loc and pattern through being a closure.
1264 |
1265 | Args:
1266 | e: Number of errors in match.
1267 | x: Location of match.
1268 |
1269 | Returns:
1270 | Overall score for match (0.0 = good, 1.0 = bad).
1271 | """
1272 | accuracy = float(e) / len(pattern)
1273 | proximity = abs(loc - x)
1274 | if not self.Match_Distance:
1275 | # Dodge divide by zero error.
1276 | return proximity and 1.0 or accuracy
1277 | return accuracy + (proximity / float(self.Match_Distance))
1278 |
1279 | # Highest score beyond which we give up.
1280 | score_threshold = self.Match_Threshold
1281 | # Is there a nearby exact match? (speedup)
1282 | best_loc = text.find(pattern, loc)
1283 | if best_loc != -1:
1284 | score_threshold = min(match_bitapScore(0, best_loc), score_threshold)
1285 | # What about in the other direction? (speedup)
1286 | best_loc = text.rfind(pattern, loc + len(pattern))
1287 | if best_loc != -1:
1288 | score_threshold = min(match_bitapScore(0, best_loc), score_threshold)
1289 |
1290 | # Initialise the bit arrays.
1291 | matchmask = 1 << (len(pattern) - 1)
1292 | best_loc = -1
1293 |
1294 | bin_max = len(pattern) + len(text)
1295 | # Empty initialization added to appease pychecker.
1296 | last_rd = None
1297 | for d in range(len(pattern)):
1298 | # Scan for the best match each iteration allows for one more error.
1299 | # Run a binary search to determine how far from 'loc' we can stray at
1300 | # this error level.
1301 | bin_min = 0
1302 | bin_mid = bin_max
1303 | while bin_min < bin_mid:
1304 | if match_bitapScore(d, loc + bin_mid) <= score_threshold:
1305 | bin_min = bin_mid
1306 | else:
1307 | bin_max = bin_mid
1308 | bin_mid = (bin_max - bin_min) // 2 + bin_min
1309 |
1310 | # Use the result from this iteration as the maximum for the next.
1311 | bin_max = bin_mid
1312 | start = max(1, loc - bin_mid + 1)
1313 | finish = min(loc + bin_mid, len(text)) + len(pattern)
1314 |
1315 | rd = [0] * (finish + 2)
1316 | rd[finish + 1] = (1 << d) - 1
1317 | for j in range(finish, start - 1, -1):
1318 | if len(text) <= j - 1:
1319 | # Out of range.
1320 | charMatch = 0
1321 | else:
1322 | charMatch = s.get(text[j - 1], 0)
1323 | if d == 0: # First pass: exact match.
1324 | rd[j] = ((rd[j + 1] << 1) | 1) & charMatch
1325 | else: # Subsequent passes: fuzzy match.
1326 | rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) | (
1327 | ((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]
1328 | if rd[j] & matchmask:
1329 | score = match_bitapScore(d, j - 1)
1330 | # This match will almost certainly be better than any existing match.
1331 | # But check anyway.
1332 | if score <= score_threshold:
1333 | # Told you so.
1334 | score_threshold = score
1335 | best_loc = j - 1
1336 | if best_loc > loc:
1337 | # When passing loc, don't exceed our current distance from loc.
1338 | start = max(1, 2 * loc - best_loc)
1339 | else:
1340 | # Already passed loc, downhill from here on in.
1341 | break
1342 | # No hope for a (better) match at greater error levels.
1343 | if match_bitapScore(d + 1, loc) > score_threshold:
1344 | break
1345 | last_rd = rd
1346 | return best_loc
1347 |
1348 | def match_alphabet(self, pattern):
1349 | """Initialise the alphabet for the Bitap algorithm.
1350 |
1351 | Args:
1352 | pattern: The text to encode.
1353 |
1354 | Returns:
1355 | Hash of character locations.
1356 | """
1357 | s = {}
1358 | for char in pattern:
1359 | s[char] = 0
1360 | for i in range(len(pattern)):
1361 | s[pattern[i]] |= 1 << (len(pattern) - i - 1)
1362 | return s
1363 |
1364 | # PATCH FUNCTIONS
1365 |
1366 | def patch_addContext(self, patch, text):
1367 | """Increase the context until it is unique,
1368 | but don't let the pattern expand beyond Match_MaxBits.
1369 |
1370 | Args:
1371 | patch: The patch to grow.
1372 | text: Source text.
1373 | """
1374 | if len(text) == 0:
1375 | return
1376 | pattern = text[patch.start2 : patch.start2 + patch.length1]
1377 | padding = 0
1378 |
1379 | # Look for the first and last matches of pattern in text. If two different
1380 | # matches are found, increase the pattern length.
1381 | while (text.find(pattern) != text.rfind(pattern) and (self.Match_MaxBits ==
1382 | 0 or len(pattern) < self.Match_MaxBits - self.Patch_Margin -
1383 | self.Patch_Margin)):
1384 | padding += self.Patch_Margin
1385 | pattern = text[max(0, patch.start2 - padding) :
1386 | patch.start2 + patch.length1 + padding]
1387 | # Add one chunk for good luck.
1388 | padding += self.Patch_Margin
1389 |
1390 | # Add the prefix.
1391 | prefix = text[max(0, patch.start2 - padding) : patch.start2]
1392 | if prefix:
1393 | patch.diffs[:0] = [(self.DIFF_EQUAL, prefix)]
1394 | # Add the suffix.
1395 | suffix = text[patch.start2 + patch.length1 :
1396 | patch.start2 + patch.length1 + padding]
1397 | if suffix:
1398 | patch.diffs.append((self.DIFF_EQUAL, suffix))
1399 |
1400 | # Roll back the start points.
1401 | patch.start1 -= len(prefix)
1402 | patch.start2 -= len(prefix)
1403 | # Extend lengths.
1404 | patch.length1 += len(prefix) + len(suffix)
1405 | patch.length2 += len(prefix) + len(suffix)
1406 |
1407 | def patch_make(self, a, b=None, c=None):
1408 | """Compute a list of patches to turn text1 into text2.
1409 | Use diffs if provided, otherwise compute it ourselves.
1410 | There are four ways to call this function, depending on what data is
1411 | available to the caller:
1412 | Method 1:
1413 | a = text1, b = text2
1414 | Method 2:
1415 | a = diffs
1416 | Method 3 (optimal):
1417 | a = text1, b = diffs
1418 | Method 4 (deprecated, use method 3):
1419 | a = text1, b = text2, c = diffs
1420 |
1421 | Args:
1422 | a: text1 (methods 1,3,4) or Array of diff tuples for text1 to
1423 | text2 (method 2).
1424 | b: text2 (methods 1,4) or Array of diff tuples for text1 to
1425 | text2 (method 3) or undefined (method 2).
1426 | c: Array of diff tuples for text1 to text2 (method 4) or
1427 | undefined (methods 1,2,3).
1428 |
1429 | Returns:
1430 | Array of Patch objects.
1431 | """
1432 | text1 = None
1433 | diffs = None
1434 | if isinstance(a, str) and isinstance(b, str) and c is None:
1435 | # Method 1: text1, text2
1436 | # Compute diffs from text1 and text2.
1437 | text1 = a
1438 | diffs = self.diff_main(text1, b, True)
1439 | if len(diffs) > 2:
1440 | self.diff_cleanupSemantic(diffs)
1441 | self.diff_cleanupEfficiency(diffs)
1442 | elif isinstance(a, list) and b is None and c is None:
1443 | # Method 2: diffs
1444 | # Compute text1 from diffs.
1445 | diffs = a
1446 | text1 = self.diff_text1(diffs)
1447 | elif isinstance(a, str) and isinstance(b, list) and c is None:
1448 | # Method 3: text1, diffs
1449 | text1 = a
1450 | diffs = b
1451 | elif (isinstance(a, str) and isinstance(b, str) and
1452 | isinstance(c, list)):
1453 | # Method 4: text1, text2, diffs
1454 | # text2 is not used.
1455 | text1 = a
1456 | diffs = c
1457 | else:
1458 | raise ValueError("Unknown call format to patch_make.")
1459 |
1460 | if not diffs:
1461 | return [] # Get rid of the None case.
1462 | patches = []
1463 | patch = patch_obj()
1464 | char_count1 = 0 # Number of characters into the text1 string.
1465 | char_count2 = 0 # Number of characters into the text2 string.
1466 | prepatch_text = text1 # Recreate the patches to determine context info.
1467 | postpatch_text = text1
1468 | for x in range(len(diffs)):
1469 | (diff_type, diff_text) = diffs[x]
1470 | if len(patch.diffs) == 0 and diff_type != self.DIFF_EQUAL:
1471 | # A new patch starts here.
1472 | patch.start1 = char_count1
1473 | patch.start2 = char_count2
1474 | if diff_type == self.DIFF_INSERT:
1475 | # Insertion
1476 | patch.diffs.append(diffs[x])
1477 | patch.length2 += len(diff_text)
1478 | postpatch_text = (postpatch_text[:char_count2] + diff_text +
1479 | postpatch_text[char_count2:])
1480 | elif diff_type == self.DIFF_DELETE:
1481 | # Deletion.
1482 | patch.length1 += len(diff_text)
1483 | patch.diffs.append(diffs[x])
1484 | postpatch_text = (postpatch_text[:char_count2] +
1485 | postpatch_text[char_count2 + len(diff_text):])
1486 | elif (diff_type == self.DIFF_EQUAL and
1487 | len(diff_text) <= 2 * self.Patch_Margin and
1488 | len(patch.diffs) != 0 and len(diffs) != x + 1):
1489 | # Small equality inside a patch.
1490 | patch.diffs.append(diffs[x])
1491 | patch.length1 += len(diff_text)
1492 | patch.length2 += len(diff_text)
1493 |
1494 | if (diff_type == self.DIFF_EQUAL and
1495 | len(diff_text) >= 2 * self.Patch_Margin):
1496 | # Time for a new patch.
1497 | if len(patch.diffs) != 0:
1498 | self.patch_addContext(patch, prepatch_text)
1499 | patches.append(patch)
1500 | patch = patch_obj()
1501 | # Unlike Unidiff, our patch lists have a rolling context.
1502 | # http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
1503 | # Update prepatch text & pos to reflect the application of the
1504 | # just completed patch.
1505 | prepatch_text = postpatch_text
1506 | char_count1 = char_count2
1507 |
1508 | # Update the current character count.
1509 | if diff_type != self.DIFF_INSERT:
1510 | char_count1 += len(diff_text)
1511 | if diff_type != self.DIFF_DELETE:
1512 | char_count2 += len(diff_text)
1513 |
1514 | # Pick up the leftover patch if not empty.
1515 | if len(patch.diffs) != 0:
1516 | self.patch_addContext(patch, prepatch_text)
1517 | patches.append(patch)
1518 | return patches
1519 |
1520 | def patch_deepCopy(self, patches):
1521 | """Given an array of patches, return another array that is identical.
1522 |
1523 | Args:
1524 | patches: Array of Patch objects.
1525 |
1526 | Returns:
1527 | Array of Patch objects.
1528 | """
1529 | patchesCopy = []
1530 | for patch in patches:
1531 | patchCopy = patch_obj()
1532 | # No need to deep copy the tuples since they are immutable.
1533 | patchCopy.diffs = patch.diffs[:]
1534 | patchCopy.start1 = patch.start1
1535 | patchCopy.start2 = patch.start2
1536 | patchCopy.length1 = patch.length1
1537 | patchCopy.length2 = patch.length2
1538 | patchesCopy.append(patchCopy)
1539 | return patchesCopy
1540 |
1541 | def patch_apply(self, patches, text):
1542 | """Merge a set of patches onto the text. Return a patched text, as well
1543 | as a list of true/false values indicating which patches were applied.
1544 |
1545 | Args:
1546 | patches: Array of Patch objects.
1547 | text: Old text.
1548 |
1549 | Returns:
1550 | Two element Array, containing the new text and an array of boolean values.
1551 | """
1552 | if not patches:
1553 | return (text, [])
1554 |
1555 | # Deep copy the patches so that no changes are made to originals.
1556 | patches = self.patch_deepCopy(patches)
1557 |
1558 | nullPadding = self.patch_addPadding(patches)
1559 | text = nullPadding + text + nullPadding
1560 | self.patch_splitMax(patches)
1561 |
1562 | # delta keeps track of the offset between the expected and actual location
1563 | # of the previous patch. If there are patches expected at positions 10 and
1564 | # 20, but the first patch was found at 12, delta is 2 and the second patch
1565 | # has an effective expected position of 22.
1566 | delta = 0
1567 | results = []
1568 | for patch in patches:
1569 | expected_loc = patch.start2 + delta
1570 | text1 = self.diff_text1(patch.diffs)
1571 | end_loc = -1
1572 | if len(text1) > self.Match_MaxBits:
1573 | # patch_splitMax will only provide an oversized pattern in the case of
1574 | # a monster delete.
1575 | start_loc = self.match_main(text, text1[:self.Match_MaxBits],
1576 | expected_loc)
1577 | if start_loc != -1:
1578 | end_loc = self.match_main(text, text1[-self.Match_MaxBits:],
1579 | expected_loc + len(text1) - self.Match_MaxBits)
1580 | if end_loc == -1 or start_loc >= end_loc:
1581 | # Can't find valid trailing context. Drop this patch.
1582 | start_loc = -1
1583 | else:
1584 | start_loc = self.match_main(text, text1, expected_loc)
1585 | if start_loc == -1:
1586 | # No match found. :(
1587 | results.append(False)
1588 | # Subtract the delta for this failed patch from subsequent patches.
1589 | delta -= patch.length2 - patch.length1
1590 | else:
1591 | # Found a match. :)
1592 | results.append(True)
1593 | delta = start_loc - expected_loc
1594 | if end_loc == -1:
1595 | text2 = text[start_loc : start_loc + len(text1)]
1596 | else:
1597 | text2 = text[start_loc : end_loc + self.Match_MaxBits]
1598 | if text1 == text2:
1599 | # Perfect match, just shove the replacement text in.
1600 | text = (text[:start_loc] + self.diff_text2(patch.diffs) +
1601 | text[start_loc + len(text1):])
1602 | else:
1603 | # Imperfect match.
1604 | # Run a diff to get a framework of equivalent indices.
1605 | diffs = self.diff_main(text1, text2, False)
1606 | if (len(text1) > self.Match_MaxBits and
1607 | self.diff_levenshtein(diffs) / float(len(text1)) >
1608 | self.Patch_DeleteThreshold):
1609 | # The end points match, but the content is unacceptably bad.
1610 | results[-1] = False
1611 | else:
1612 | self.diff_cleanupSemanticLossless(diffs)
1613 | index1 = 0
1614 | for (op, data) in patch.diffs:
1615 | if op != self.DIFF_EQUAL:
1616 | index2 = self.diff_xIndex(diffs, index1)
1617 | if op == self.DIFF_INSERT: # Insertion
1618 | text = text[:start_loc + index2] + data + text[start_loc +
1619 | index2:]
1620 | elif op == self.DIFF_DELETE: # Deletion
1621 | text = text[:start_loc + index2] + text[start_loc +
1622 | self.diff_xIndex(diffs, index1 + len(data)):]
1623 | if op != self.DIFF_DELETE:
1624 | index1 += len(data)
1625 | # Strip the padding off.
1626 | text = text[len(nullPadding):-len(nullPadding)]
1627 | return (text, results)
1628 |
1629 | def patch_addPadding(self, patches):
1630 | """Add some padding on text start and end so that edges can match
1631 | something. Intended to be called only from within patch_apply.
1632 |
1633 | Args:
1634 | patches: Array of Patch objects.
1635 |
1636 | Returns:
1637 | The padding string added to each side.
1638 | """
1639 | paddingLength = self.Patch_Margin
1640 | nullPadding = ""
1641 | for x in range(1, paddingLength + 1):
1642 | nullPadding += chr(x)
1643 |
1644 | # Bump all the patches forward.
1645 | for patch in patches:
1646 | patch.start1 += paddingLength
1647 | patch.start2 += paddingLength
1648 |
1649 | # Add some padding on start of first diff.
1650 | patch = patches[0]
1651 | diffs = patch.diffs
1652 | if not diffs or diffs[0][0] != self.DIFF_EQUAL:
1653 | # Add nullPadding equality.
1654 | diffs.insert(0, (self.DIFF_EQUAL, nullPadding))
1655 | patch.start1 -= paddingLength # Should be 0.
1656 | patch.start2 -= paddingLength # Should be 0.
1657 | patch.length1 += paddingLength
1658 | patch.length2 += paddingLength
1659 | elif paddingLength > len(diffs[0][1]):
1660 | # Grow first equality.
1661 | extraLength = paddingLength - len(diffs[0][1])
1662 | newText = nullPadding[len(diffs[0][1]):] + diffs[0][1]
1663 | diffs[0] = (diffs[0][0], newText)
1664 | patch.start1 -= extraLength
1665 | patch.start2 -= extraLength
1666 | patch.length1 += extraLength
1667 | patch.length2 += extraLength
1668 |
1669 | # Add some padding on end of last diff.
1670 | patch = patches[-1]
1671 | diffs = patch.diffs
1672 | if not diffs or diffs[-1][0] != self.DIFF_EQUAL:
1673 | # Add nullPadding equality.
1674 | diffs.append((self.DIFF_EQUAL, nullPadding))
1675 | patch.length1 += paddingLength
1676 | patch.length2 += paddingLength
1677 | elif paddingLength > len(diffs[-1][1]):
1678 | # Grow last equality.
1679 | extraLength = paddingLength - len(diffs[-1][1])
1680 | newText = diffs[-1][1] + nullPadding[:extraLength]
1681 | diffs[-1] = (diffs[-1][0], newText)
1682 | patch.length1 += extraLength
1683 | patch.length2 += extraLength
1684 |
1685 | return nullPadding
1686 |
1687 | def patch_splitMax(self, patches):
1688 | """Look through the patches and break up any which are longer than the
1689 | maximum limit of the match algorithm.
1690 | Intended to be called only from within patch_apply.
1691 |
1692 | Args:
1693 | patches: Array of Patch objects.
1694 | """
1695 | patch_size = self.Match_MaxBits
1696 | if patch_size == 0:
1697 | # Python has the option of not splitting strings due to its ability
1698 | # to handle integers of arbitrary precision.
1699 | return
1700 | for x in range(len(patches)):
1701 | if patches[x].length1 <= patch_size:
1702 | continue
1703 | bigpatch = patches[x]
1704 | # Remove the big old patch.
1705 | del patches[x]
1706 | x -= 1
1707 | start1 = bigpatch.start1
1708 | start2 = bigpatch.start2
1709 | precontext = ''
1710 | while len(bigpatch.diffs) != 0:
1711 | # Create one of several smaller patches.
1712 | patch = patch_obj()
1713 | empty = True
1714 | patch.start1 = start1 - len(precontext)
1715 | patch.start2 = start2 - len(precontext)
1716 | if precontext:
1717 | patch.length1 = patch.length2 = len(precontext)
1718 | patch.diffs.append((self.DIFF_EQUAL, precontext))
1719 |
1720 | while (len(bigpatch.diffs) != 0 and
1721 | patch.length1 < patch_size - self.Patch_Margin):
1722 | (diff_type, diff_text) = bigpatch.diffs[0]
1723 | if diff_type == self.DIFF_INSERT:
1724 | # Insertions are harmless.
1725 | patch.length2 += len(diff_text)
1726 | start2 += len(diff_text)
1727 | patch.diffs.append(bigpatch.diffs.pop(0))
1728 | empty = False
1729 | elif (diff_type == self.DIFF_DELETE and len(patch.diffs) == 1 and
1730 | patch.diffs[0][0] == self.DIFF_EQUAL and
1731 | len(diff_text) > 2 * patch_size):
1732 | # This is a large deletion. Let it pass in one chunk.
1733 | patch.length1 += len(diff_text)
1734 | start1 += len(diff_text)
1735 | empty = False
1736 | patch.diffs.append((diff_type, diff_text))
1737 | del bigpatch.diffs[0]
1738 | else:
1739 | # Deletion or equality. Only take as much as we can stomach.
1740 | diff_text = diff_text[:patch_size - patch.length1 -
1741 | self.Patch_Margin]
1742 | patch.length1 += len(diff_text)
1743 | start1 += len(diff_text)
1744 | if diff_type == self.DIFF_EQUAL:
1745 | patch.length2 += len(diff_text)
1746 | start2 += len(diff_text)
1747 | else:
1748 | empty = False
1749 |
1750 | patch.diffs.append((diff_type, diff_text))
1751 | if diff_text == bigpatch.diffs[0][1]:
1752 | del bigpatch.diffs[0]
1753 | else:
1754 | bigpatch.diffs[0] = (bigpatch.diffs[0][0],
1755 | bigpatch.diffs[0][1][len(diff_text):])
1756 |
1757 | # Compute the head context for the next patch.
1758 | precontext = self.diff_text2(patch.diffs)
1759 | precontext = precontext[-self.Patch_Margin:]
1760 | # Append the end context for this patch.
1761 | postcontext = self.diff_text1(bigpatch.diffs)[:self.Patch_Margin]
1762 | if postcontext:
1763 | patch.length1 += len(postcontext)
1764 | patch.length2 += len(postcontext)
1765 | if len(patch.diffs) != 0 and patch.diffs[-1][0] == self.DIFF_EQUAL:
1766 | patch.diffs[-1] = (self.DIFF_EQUAL, patch.diffs[-1][1] +
1767 | postcontext)
1768 | else:
1769 | patch.diffs.append((self.DIFF_EQUAL, postcontext))
1770 |
1771 | if not empty:
1772 | x += 1
1773 | patches.insert(x, patch)
1774 |
1775 | def patch_toText(self, patches):
1776 | """Take a list of patches and return a textual representation.
1777 |
1778 | Args:
1779 | patches: Array of Patch objects.
1780 |
1781 | Returns:
1782 | Text representation of patches.
1783 | """
1784 | text = []
1785 | for patch in patches:
1786 | text.append(str(patch))
1787 | return "".join(text)
1788 |
1789 | def patch_fromText(self, textline):
1790 | """Parse a textual representation of patches and return a list of patch
1791 | objects.
1792 |
1793 | Args:
1794 | textline: Text representation of patches.
1795 |
1796 | Returns:
1797 | Array of Patch objects.
1798 |
1799 | Raises:
1800 | ValueError: If invalid input.
1801 | """
1802 | patches = []
1803 | if not textline:
1804 | return patches
1805 | text = textline.split('\n')
1806 | while len(text) != 0:
1807 | m = re.match("^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$", text[0])
1808 | if not m:
1809 | raise ValueError("Invalid patch string: " + text[0])
1810 | patch = patch_obj()
1811 | patches.append(patch)
1812 | patch.start1 = int(m.group(1))
1813 | if m.group(2) == '':
1814 | patch.start1 -= 1
1815 | patch.length1 = 1
1816 | elif m.group(2) == '0':
1817 | patch.length1 = 0
1818 | else:
1819 | patch.start1 -= 1
1820 | patch.length1 = int(m.group(2))
1821 |
1822 | patch.start2 = int(m.group(3))
1823 | if m.group(4) == '':
1824 | patch.start2 -= 1
1825 | patch.length2 = 1
1826 | elif m.group(4) == '0':
1827 | patch.length2 = 0
1828 | else:
1829 | patch.start2 -= 1
1830 | patch.length2 = int(m.group(4))
1831 |
1832 | del text[0]
1833 |
1834 | while len(text) != 0:
1835 | if text[0]:
1836 | sign = text[0][0]
1837 | else:
1838 | sign = ''
1839 | line = urllib.parse.unquote(text[0][1:])
1840 | if sign == '+':
1841 | # Insertion.
1842 | patch.diffs.append((self.DIFF_INSERT, line))
1843 | elif sign == '-':
1844 | # Deletion.
1845 | patch.diffs.append((self.DIFF_DELETE, line))
1846 | elif sign == ' ':
1847 | # Minor equality.
1848 | patch.diffs.append((self.DIFF_EQUAL, line))
1849 | elif sign == '@':
1850 | # Start of next patch.
1851 | break
1852 | elif sign == '':
1853 | # Blank line? Whatever.
1854 | pass
1855 | else:
1856 | # WTF?
1857 | raise ValueError("Invalid patch mode: '%s'\n%s" % (sign, line))
1858 | del text[0]
1859 | return patches
1860 |
1861 |
1862 | class patch_obj:
1863 | """Class representing one patch operation.
1864 | """
1865 |
1866 | def __init__(self):
1867 | """Initializes with an empty list of diffs.
1868 | """
1869 | self.diffs = []
1870 | self.start1 = None
1871 | self.start2 = None
1872 | self.length1 = 0
1873 | self.length2 = 0
1874 |
1875 | def __str__(self):
1876 | """Emmulate GNU diff's format.
1877 | Header: @@ -382,8 +481,9 @@
1878 | Indicies are printed as 1-based, not 0-based.
1879 |
1880 | Returns:
1881 | The GNU diff string.
1882 | """
1883 | if self.length1 == 0:
1884 | coords1 = str(self.start1) + ",0"
1885 | elif self.length1 == 1:
1886 | coords1 = str(self.start1 + 1)
1887 | else:
1888 | coords1 = str(self.start1 + 1) + "," + str(self.length1)
1889 | if self.length2 == 0:
1890 | coords2 = str(self.start2) + ",0"
1891 | elif self.length2 == 1:
1892 | coords2 = str(self.start2 + 1)
1893 | else:
1894 | coords2 = str(self.start2 + 1) + "," + str(self.length2)
1895 | text = ["@@ -", coords1, " +", coords2, " @@\n"]
1896 | # Escape the body of the patch with %xx notation.
1897 | for (op, data) in self.diffs:
1898 | if op == diff_match_patch.DIFF_INSERT:
1899 | text.append("+")
1900 | elif op == diff_match_patch.DIFF_DELETE:
1901 | text.append("-")
1902 | elif op == diff_match_patch.DIFF_EQUAL:
1903 | text.append(" ")
1904 | # High ascii will raise UnicodeDecodeError. Use Unicode instead.
1905 | data = data.encode("utf-8")
1906 | text.append(urllib.parse.quote(data, "!~*'();/?:@&=+$,# ") + "\n")
1907 | return "".join(text)
1908 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | v3.1.2 (04/04/2015)
2 |
3 | * Add user friendly error messages for invalid options.
4 | * Strip invalid options in astylerc.
5 |
6 | v3.1.1 (02/28/2015)
7 |
8 | * Hot-Fix for mysterious ValueError(), introduced in v3.1.0.
9 |
10 | v3.1.0 (02/28/2015)
11 |
12 | * The following default options were changed, according to *Artistic Style* defaults:
13 | * `indent-preproc-define`: `true` -> `false`
14 | * `indent-col1-comments`: `true` -> `false`
15 | * `align-pointer`: `"name"` -> "not set" (Use *Artistic Style* defaults, which is "no change")
16 | * `align-reference`: `"name"` -> "not set" (Use *Artistic Style* defaults, which is "same as `align-pointer`")
17 | * `keep-one-line-blocks`: `false` -> `true`
18 | * Now an output panel with user-friendly error message will show if anything goes wrong.
19 | * Remove `convert-tabs` option, because it's duplicated with Sublime Text's `translate_tabs_to_spaces` setting.
20 | * Fix a NoneType error while formatting unsaved files.
21 |
22 | v3.0.0 (02/20/2015)
23 |
24 | * Upgrade astyle binary to v2.05.1.
25 | * Improper configuration settings will raise errors now.
26 | * Fix missing "google" style option.
27 | * Remove "ansi" style option because it's deprecated in astyle v2.05.
28 | * Remove "indent-preprocessor" option because it was deprecated in astyle v2.04.
29 | * Add "indent-preproc-block" option (introduced in astyle v2.05).
30 | * Add more expanded variables for reaching astylerc file (see `SublimeAStyleFormatter.sublime-settings` for more details, in `additional_options_file`).
31 |
32 | v2.1.0 (04/23/2014)
33 |
34 | * Upgrade astyle binary to v2.04.
35 | * Fix unfunctional `user_defined_syntax_mode_mapping` option.
36 | * Fix wrong user keymap setting file location.
37 |
38 | v2.0.5 (11/28/2013)
39 |
40 | * Fix plugin stop working while `additional_options` missing from user options.
41 | * Add `apex` syntax support.
42 | * Add new option: `user_defined_syntax_mode_mapping`.
43 |
44 | v2.0.4 (10/30/2013)
45 |
46 | * Fix OSX and Linux pyastyle binaries.
47 |
48 | v2.0.3 (10/28/2013)
49 |
50 | * Add Arduino files support.
51 |
52 | v2.0.2 (07/19/2013)
53 |
54 | * Less error-prone default options overriding (You don't need to duplicate whole
55 | `options_defaut` section before customizing now, default options in `options_defaut`
56 | section will be retrieved automatically).
57 |
58 | v2.0.1 (06/26/2013)
59 |
60 | * Rebuild pyastyle libraries for linux amd64 in order to resolve libc/libc++ compatibility issues.
61 |
62 | v2.0.0 (06/22/2013)
63 |
64 | * Update Artistic Style to v2.03 release ([News](http://astyle.sourceforge.net/news.html)
65 | and [Release Notes](http://astyle.sourceforge.net/notes.html)).
66 | * Please note that deprecated bracket options are now removed from astyle v2.03, use
67 | `style` options instead if you have any those deprecated options (usually in your `astylerc` files).
68 | * Add new options: `pad-first-paren-out`, `close-templates`, `max-code-length` and `break-after-logical`.
69 |
70 | v1.9.4 (04/16/2013)
71 |
72 | * Add OpenCL and Cuda-C++ (each requires its syntax file installed) support.
73 |
74 | v1.9.3 (03/24/2013)
75 |
76 | * Can be now installed from Package Control (latest) for Sublime Text 3.
77 |
78 | v1.9.2 (03/16/2013)
79 |
80 | * Add OS X support for Sublime Text 3.
81 |
82 | v1.9.1 (03/10/2013)
83 |
84 | * Add Linux support (Both x86 and x86_64) for Sublime Text 3.
85 |
86 | v1.9 (03/08/2013)
87 |
88 | * Preliminary support for Sublime Text 3 (Now only Windows x86 and Windows x86_64).
89 |
90 | v1.8 (12/24/2012)
91 |
92 | * Add auto format on file save (through option `autoformat_on_save`).
93 | * Add context and side bar commands.
94 |
95 | v1.7.3 (12/18/2012)
96 |
97 | * Fix a conflict with SublimeCodeIntel.
98 |
99 | v1.7.1 (11/22/2012)
100 |
101 | * Change default keybinding for OSX (was conflict with "replace" in Sublime Text 2).
102 |
103 | v1.7 (11/10/2012)
104 |
105 | * Buffer will not scroll up and down during formatting now.
106 |
107 | v1.6.2 (11/5/2012)
108 |
109 | * Rebuild pyastyle x86_64 binary which should work on older version of linux distros.
110 |
111 | v1.6.1 (10/20/2012)
112 |
113 | * Fix ascii decoder error if source contains non-ascii characters.
114 |
115 | v1.6 (10/19/2012)
116 |
117 | * Remove dependency for ctypes.
118 |
119 | v1.5 (10/16/2012)
120 |
121 | * Update AStyle binrary of OSX.
122 | * Add meaningful prompt dialog while ctypes module cannot be imported in Linux.
123 |
124 | v1.4.1 (10/6/2012)
125 |
126 | * Fix wrong AStyle.dll for 32bit Windows.
127 |
128 | v1.4 (9/28/2012)
129 |
130 | * Add linux binaries (ctypes should be installed manually in order to get it work).
131 | * Fix default key binding conflicts with JsFormat (ctrl+alt+f).
132 | * Windows and Linux astyle libraries are now v2.0.3 beta.
133 |
134 | v1.3 (9/21/2012)
135 |
136 | * Added support for formatting selection text only.
137 | * Restore to previous viewport after formatting entire file.
138 |
139 | v1.2 (4/19/2012)
140 |
141 | * Added support for per-project settings.
142 | * Fixed a bug that "additional_options" is invalid when "use_only_additional_options " is not "true".
143 | * Fixed a bug which will throw python 'KeyError' exception while options in "options_default" are lesser than expected.
144 |
145 | v1.1 (2/5/2012)
146 |
147 | * Added support for OS X.
148 | * More comprehensive options.
149 |
--------------------------------------------------------------------------------
/Context.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | { "caption": "-", "id": "file" },
3 | {
4 | "caption": "AStyleFormatter",
5 | "children":
6 | [
7 | { "caption": "Format", "command": "astyleformat" },
8 | {
9 | "caption": "Format selection", "command": "astyleformat",
10 | "args": {"selection_only": true}
11 | }
12 | ]
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/DONORS.md:
--------------------------------------------------------------------------------
1 | # Donors
2 |
3 | Here is a list of donors, thanks all for your contribution/contributions!
4 |
5 | (If you would like your entry to be updated or be removed, please contact me)
6 |
7 | * [Davis Clark](https://github.com/jdc0589)
8 | * Zhong Ying
9 | * Conner Bryan
10 |
--------------------------------------------------------------------------------
/Default (Linux).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "keys": ["ctrl+alt+f"], "command": "astyleformat",
4 | "context": [{"key": "astyleformat_is_enabled", "operator": "equal", "operand": ""}]
5 | },
6 | {
7 | "keys": ["ctrl+k", "ctrl+f"], "command": "astyleformat", "args": {"selection_only": true},
8 | "context": [{"key": "astyleformat_is_enabled", "operator": "equal", "operand": ""}]
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/Default (OSX).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "keys": ["ctrl+alt+f"], "command": "astyleformat",
4 | "context": [{"key": "astyleformat_is_enabled", "operator": "equal", "operand": ""}]
5 | },
6 | {
7 | "keys": ["super+k", "super+f"], "command": "astyleformat", "args": {"selection_only": true},
8 | "context": [{"key": "astyleformat_is_enabled", "operator": "equal", "operand": ""}]
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/Default (Windows).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "keys": ["ctrl+alt+f"], "command": "astyleformat",
4 | "context": [{"key": "astyleformat_is_enabled", "operator": "equal", "operand": ""}]
5 | },
6 | {
7 | "keys": ["ctrl+k", "ctrl+f"], "command": "astyleformat", "args": {"selection_only": true},
8 | "context": [{"key": "astyleformat_is_enabled", "operator": "equal", "operand": ""}]
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2015 Timon Wong
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Main.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Preferences",
4 | "mnemonic": "n",
5 | "id": "preferences",
6 | "children":
7 | [
8 | {
9 | "caption": "Package Settings",
10 | "mnemonic": "P",
11 | "id": "package-settings",
12 | "children":
13 | [
14 | {
15 | "id": "SublimeAStyleFormatter",
16 | "caption": "SublimeAStyleFormatter",
17 | "children":
18 | [
19 | {
20 | "command": "open_file", "args":
21 | {
22 | "file": "${packages}/SublimeAStyleFormatter/SublimeAStyleFormatter.sublime-settings"
23 | },
24 | "caption": "Settings – Default"
25 | },
26 | {
27 | "command": "open_file", "args":
28 | {
29 | "file": "${packages}/User/SublimeAStyleFormatter.sublime-settings"
30 | },
31 | "caption": "Settings – User"
32 | },
33 | { "caption": "-" },
34 | {
35 | "command": "open_file", "args":
36 | {
37 | "file": "${packages}/SublimeAStyleFormatter/Default (OSX).sublime-keymap",
38 | "platform": "OSX"
39 | },
40 | "caption": "Key Bindings – Default"
41 | },
42 | {
43 | "command": "open_file", "args":
44 | {
45 | "file": "${packages}/SublimeAStyleFormatter/Default (Linux).sublime-keymap",
46 | "platform": "Linux"
47 | },
48 | "caption": "Key Bindings – Default"
49 | },
50 | {
51 | "command": "open_file",
52 | "args": {
53 | "file": "${packages}/SublimeAStyleFormatter/Default (Windows).sublime-keymap",
54 | "platform": "Windows"
55 | },
56 | "caption": "Key Bindings – Default"
57 | },
58 | {
59 | "command": "open_file",
60 | "args": {
61 | "file": "${packages}/User/Default (OSX).sublime-keymap",
62 | "platform": "OSX"
63 | },
64 | "caption": "Key Bindings – User"
65 | },
66 | {
67 | "command": "open_file",
68 | "args": {
69 | "file": "${packages}/User/Default (Linux).sublime-keymap",
70 | "platform": "Linux"
71 | },
72 | "caption": "Key Bindings – User"
73 | },
74 | {
75 | "command": "open_file",
76 | "args": {
77 | "file": "${packages}/User/Default (Windows).sublime-keymap",
78 | "platform": "Windows"
79 | },
80 | "caption": "Key Bindings – User"
81 | }
82 | ]
83 | }
84 | ]
85 | }
86 | ]
87 | }
88 | ]
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Sublime Text 2 & 3 AStyle Formatter Plugin
2 | ==========================================
3 |
4 | [](https://travis-ci.org/timonwong/SublimeAStyleFormatter)
5 | [](https://ci.appveyor.com/project/timonwong/SublimeAStyleFormatter)
6 |
7 | Description
8 | -----------
9 |
10 | SublimeAStyleFormatter is a simple code formatter plugin for Sublime Text.
11 | It provides ability to format C, C++, Cuda-C++, OpenCL, Arduino, C#, and Java files.
12 |
13 | **NOTE**: Syntax files required to be installed separately for Cuda-C++ and OpenCL.
14 |
15 | ### Donation
16 |
17 | If you find my work useful, please consider buying me a cup of coffee, all
18 | donations are much appreciated :)
19 |
20 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GGVE2BPUP7KEC)
21 |
22 | Installation
23 | ------------
24 |
25 | ### With the Package Control plugin
26 |
27 | The easiest way to install SublimeAStyleFormatter is through [Package Control].
28 |
29 | [Package Control]: http://wbond.net/sublime_packages/package_control
30 |
31 | Once you have Package Control installed, restart Sublime Text.
32 |
33 | 1. Bring up the Command Palette (Ctrl+Shift+P
34 | on Windows and Linux. ⌘+⇧+P on OS X).
35 | 2. Type "Install" and select "Package Control: Install Package".
36 | 3. Select "SublimeAStyleFormatter" from list.
37 |
38 | The advantage of using Package Control is that it will keep SublimeAStyleFormatter up to date.
39 |
40 | ### Manual Install
41 |
42 | **Without Git:**
43 |
44 | [Download](https://github.com/timonwong/SublimeAStyleFormatter) the latest source code,
45 | and extract it to the Packages directory.
46 |
47 | **With Git:**
48 |
49 | Type the following command in your Sublime Text 2 or Sublime Text 3 Packages directory:
50 |
51 | `git clone git://github.com/timonwong/SublimeAStyleFormatter.git`
52 |
53 | The "Packages" directory is located at:
54 |
55 | **Sublime Text 2**
56 |
57 | * **Windows**: `%APPDATA%\Sublime Text 2\Packages`
58 | * **Linux**: `~/.config/sublime-text-2/Packages/`
59 | * **OS X**: `~/Library/Application Support/Sublime Text 2/Packages/`
60 |
61 | **Sublime Text 3**
62 |
63 | * **Windows**: `%APPDATA%\Sublime Text 3\Packages`
64 | * **Linux**: `~/.config/sublime-text-3/Packages/`
65 | * **OS X**: `~/Library/Application Support/Sublime Text 3/Packages/`
66 |
67 | Usage
68 | -----
69 |
70 | ### Key Bindings
71 |
72 | The default key bindings for this plugin:
73 |
74 | **Windows, Linux:**
75 |
76 | * Ctrl+Alt+F: Format current file.
77 | * Ctrl+K, Ctrl+F: Format current selection.
78 |
79 | **OSX:**
80 |
81 | * Ctrl+Alt+F: Format current file.
82 | * ⌘+K, ⌘+F: Format current selection.
83 |
84 | ### Command Palette
85 |
86 | Open the command palette, it appears as `SublimeAStyleFormatter: Format Current File` and
87 | `SublimeAStyleFormatter Format Current Selection`.
88 |
89 | Settings
90 | --------
91 |
92 | ### Per-project Settings
93 |
94 | Before starting, you may want to have a look at SublimeAStyleFormatter.sublime-settings.
95 |
96 | To edit your project setting, select `Project/Edit Project` from main menu. A project setting contains
97 | per-project settings for SublimeAStyleFormatter should look like this:
98 |
99 | ```javascript
100 | {
101 | "settings":
102 | {
103 | "AStyleFormatter":
104 | {
105 | }
106 | }
107 | }
108 | ```
109 |
110 | For example, if you don't want to inherit the default settings, instead, use your own astylerc file for
111 | C and C++ individually, then your project setting might look like this:
112 |
113 | ```javascript
114 | {
115 | // project folders, etc
116 | // ...
117 | // project settings
118 | "settings":
119 | {
120 | "AStyleFormatter":
121 | {
122 | "options_default":
123 | {
124 | // Use 2 spaces for indentation
125 | "indent": "spaces",
126 | "indent-spaces": 2
127 | },
128 | "options_c":
129 | {
130 | "use_only_additional_options": true,
131 | "additional_options_file": "/path/to/your/astylerc/for/c"
132 | },
133 | "options_c++":
134 | {
135 | "use_only_additional_options": true,
136 | "additional_options_file": "/path/to/your/astylerc/for/c++"
137 | }
138 | }
139 | }
140 | }
141 | ```
142 |
143 |
144 | What's New
145 | -------------
146 |
147 | [CHANGELOG.md](./CHANGELOG.md)
148 |
149 |
150 | License
151 | ------
152 |
153 | This plugin is using MIT License:
154 |
155 | Copyright (c) 2012-2015 Timon Wong
156 |
157 | Permission is hereby granted, free of charge, to any person obtaining a copy of
158 | this software and associated documentation files (the "Software"), to deal in
159 | the Software without restriction, including without limitation the rights to
160 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
161 | of the Software, and to permit persons to whom the Software is furnished to do
162 | so, subject to the following conditions:
163 |
164 | The above copyright notice and this permission notice shall be included in all
165 | copies or substantial portions of the Software.
166 |
167 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
168 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
169 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
170 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
171 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
172 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
173 | SOFTWARE.
174 |
175 | Credits
176 | -------
177 |
178 | **[Artistic Style]** - A Free, Fast and Small Automatic Formatter for C, C++, C#,
179 | and Java Source Code.
180 |
181 | Licensed under [GNU Lesser General Public License version 3.0]
182 |
183 | [Artistic Style]: http://sourceforge.net/projects/astyle/
184 | [GNU Lesser General Public License version 3.0]: http://astyle.sourceforge.net/license.html
185 |
186 | Donors
187 | ------
188 |
189 | [DONORS.md](./DONORS.md)
190 |
--------------------------------------------------------------------------------
/Side Bar.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "AStyleFormatter",
4 | "children":
5 | [
6 | { "caption": "Format", "command": "astyleformat" }
7 | ]
8 | },
9 | { "caption": "-", "id": "folder_commands" }
10 | ]
11 |
--------------------------------------------------------------------------------
/SublimeAStyleFormatter.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | // NOTE: You should always edit options in user file, not this file.
3 |
4 | // Print debug message
5 | "debug": false,
6 |
7 | // Auto format on file save
8 | "autoformat_on_save": false,
9 |
10 | // The mapping key is `syntax name`, and the value is `formatting mode`.
11 | // Note that the value for each mapping should be "c", "java" or "cs".
12 | "user_defined_syntax_mode_mapping": {
13 | // For example:
14 | /*
15 | "arduino": "c",
16 | "pde": "java",
17 | "apex": "java"
18 | */
19 | },
20 |
21 | // Please visit http://astyle.sourceforge.net/astyle.html for more information
22 | "options_default": {
23 | // Default bracket style
24 | // Can be one of "allman", "bsd", "break", "java", "attach", "kr", "k&r",
25 | // "k/r" "stroustrup", "whitesmith", "banner", "gnu", "linux", "horstmann",
26 | // "1tbs", "otbs ", "google", "pico", "lisp", "python", "vtk", or null
27 | // for default.
28 | "style": null,
29 |
30 | // Tab options
31 | // "indent": Can be one of "spaces", "tab" "force-tab", "force-tab-x", or null
32 | // "indent-spaces": Can be either null or numbers
33 | //
34 | // While both "indent" or "indent-spaces" are null (default), the indentation options
35 | // will be retrieved from Sublime Text view settings: `tab_size`, `translate_tabs_to_spaces`:
36 | // 1. If `translate_tabs_to_spaces` is true, "indent" will be "spaces", otherwise, "tab";
37 | // 2. "indent-spaces" will equal to `tab_size`.
38 | "indent": null,
39 | "indent-spaces": null,
40 |
41 | // === Bracket Modify Options ===
42 | // Attach brackets to a namespace statement. This is done regardless of
43 | // the bracket style being used.
44 | "attach-namespaces": false,
45 |
46 | // Attach brackets to a class statement. This is done regardless of the
47 | // bracket style being used.
48 | "attach-classes": false,
49 |
50 | // Attach brackets to class and struct inline function definitions. This
51 | // is not done for run-in type brackets (Horstmann and Pico styles). This
52 | // option is effective for C++ files only.
53 | "attach-inlines": false,
54 |
55 | // Attach brackets to a bracketed extern "C" statement. This is done
56 | // regardless of the bracket style being used. This option is effective
57 | // for C++ files only.
58 | //
59 | // An extern "C" statement that is part of a function definition is
60 | // formatted according to the requested bracket style. Bracketed extern
61 | // "C" statements are unaffected by the bracket style and this option is
62 | // the only way to change them.
63 | "attach-extern-c": false,
64 |
65 | // === Indentation Options ===
66 |
67 | // Indent 'class' and 'struct' blocks so that the blocks 'public:',
68 | // 'protected:' and 'private:' are indented. The struct blocks are
69 | // indented only if an access modifier is declared somewhere in the
70 | // struct. The entire block is indented. This option is effective for C++ files only.
71 | "indent-classes": false,
72 |
73 | // Indent 'class' and 'struct' access modifiers, 'public:', 'protected:'
74 | // and 'private:', one half indent. The rest of the class is not indented.
75 | // This option is effective for C++ files only. If used with indent‑classes
76 | // this option will be ignored.
77 | "indent-modifiers": false,
78 |
79 | // Indent 'switch' blocks so that the 'case X:' statements are indented
80 | // in the switch block. The entire case block is indented.
81 | "indent-switches": false,
82 |
83 | // Indent 'case X:' blocks from the 'case X:' headers. Case statements
84 | // not enclosed in blocks are NOT indented.
85 | "indent-cases": false,
86 |
87 | // Add extra indentation to namespace blocks. This option has
88 | // no effect on Java files.
89 | "indent-namespaces": false,
90 |
91 | // Add extra indentation to labels so they appear 1 indent less than the
92 | // current indentation, rather than being flushed to the left (the default).
93 | "indent-labels": false,
94 |
95 | // Indent preprocessor blocks at bracket level zero, and immediately within
96 | // a namespace. There are restrictions on what will be indented. Blocks
97 | // within methods, classes, arrays, etc, will not be indented. Blocks
98 | // containing brackets or multi-line define statements will not be indented.
99 | // Without this option the preprocessor block is not indented.
100 | "indent-preproc-block": false,
101 |
102 | // Indent multi-line preprocessor definitions ending with a backslash.
103 | // Should be used with `convert-tabs` for proper results. Does a pretty
104 | // good job, but cannot perform miracles in obfuscated preprocessor
105 | // definitions. Without this option the preprocessor statements remain unchanged.
106 | "indent-preproc-define": false,
107 |
108 | // Indent preprocessor conditional statements to the same level as the
109 | // source code.
110 | "indent-preproc-cond": false,
111 |
112 | // Indent C++ comments beginning in column one. By default C++ comments
113 | // beginning in column one are not indented. This option will allow the
114 | // comments to be indented with the code.
115 | "indent-col1-comments": false,
116 |
117 | // Set the minimal indent that is added when a header is built of multiple
118 | // lines. This indent helps to easily separate the header from the command
119 | // statements that follow. The value for # indicates a number of indents
120 | // and is a minimum value. The indent may be greater to align with the data
121 | // on the previous line.
122 | // The valid values are:
123 | // 0 - no minimal indent. The lines will be aligned with the paren on the
124 | // preceding line.
125 | // 1 - indent at least one additional indent.
126 | // 2 - indent at least two additional indents.
127 | // 3 - indent at least one-half an additional indent. This is intended for
128 | // large indents (e.g. 8).
129 | // The default value is 2, two additional indents.
130 | "min-conditional-indent": 2,
131 |
132 | // Set the maximum spaces to indent a continuation line.
133 | // The maximum spaces indicate a number of columns and
134 | // must not be greater than 120. A maximum of less
135 | // than two indent lengths will be ignored. This option
136 | // will prevent continuation lines from extending too
137 | // far to the right. Setting a larger value will allow
138 | // the code to be extended further to the right.
139 | //
140 | // range: [40, 120]
141 | "max-instatement-indent": 40,
142 |
143 | // === Padding Options ===
144 |
145 | // null - Do nothing
146 | // "default" - Pad empty lines around header blocks (e.g. 'if',
147 | // 'for', 'while'...).
148 | // "all" - Pad empty lines around header blocks (e.g. 'if',
149 | // 'for', 'while'...). Treat closing header blocks
150 | // (e.g. 'else', 'catch') as stand-alone blocks.
151 | "break-blocks": null,
152 |
153 | // Insert space padding around operators. Any end of line comments
154 | // will remain in the original column, if possible. Note that there
155 | // is no option to unpad. Once padded, they stay padded.
156 | "pad-oper": true,
157 |
158 | // Insert space padding around parenthesis on both the outside and
159 | // the inside. Any end of line comments will remain in the original
160 | // column, if possible.
161 | "pad-paren": false,
162 |
163 | // Insert space padding around parenthesis on the outside only. Any
164 | // end of line comments will remain in the original column, if possible.
165 | // This can be used with `unpad-paren` below to remove unwanted spaces.
166 | "pad-paren-out": false,
167 |
168 | // Insert space padding around the first parenthesis in a series on the
169 | // outside only. Any end of line comments will remain in the original
170 | // column, if possible. This can be used with unpad-paren below to remove
171 | // unwanted spaces. If used with pad-paren or pad-paren-out, this
172 | // option will be ignored. If used with pad-paren-in, the result will
173 | // be the same as pad-paren.
174 | "pad-first-paren-out": false,
175 |
176 | // Insert space padding around parenthesis on the inside only. Any
177 | // end of line comments will remain in the original column, if possible.
178 | // This can be used with `unpad-paren` below to remove unwanted spaces.
179 | "pad-paren-in": false,
180 |
181 | // Insert space padding after paren headers only (e.g. 'if', 'for',
182 | //'while'...). Any end of line comments will remain in the original
183 | // column, if possible. This can be used with unpad-paren to remove
184 | // unwanted spaces.
185 | "pad-header": true,
186 |
187 | // Remove extra space padding around parenthesis on the inside and outside.
188 | // Any end of line comments will remain in the original column, if possible.
189 | // This option can be used in combination with the paren padding options
190 | // `pad-paren`, `pad-paren-out`, `pad-paren-in`, and `pad-header` above.
191 | // Only padding that has not been requested by other options will be removed.
192 | // For example, if a source has parens padded on both the inside and outside,
193 | // and you want inside only. You need to use unpad-paren to remove the outside
194 | // padding, and pad-paren-in to retain the inside padding. Using only `pad-paren-in`
195 | // would not remove the outside padding.
196 | "unpad-paren": false,
197 |
198 | // Delete empty lines within a function or method. Empty lines outside of functions
199 | // or methods are NOT deleted. If used with break-blocks or break-blocks=all it will
200 | // delete all lines EXCEPT the lines added by the `break-blocks` options.
201 | "delete-empty-lines": false,
202 |
203 | // Fill empty lines with the white space of the previous line.
204 | "fill-empty-lines": false,
205 |
206 | // Attach a pointer or reference operator (* or &) to either the variable type (left)
207 | // or variable name (right), or place it between the type and name (middle).
208 | // The spacing between the type and name will be preserved, if possible. To format
209 | // references separately use the following `align-reference` option.
210 | // can be one of null, "type", "middle" or "name"
211 | "align-pointer": null,
212 |
213 | // This option will align references separate from pointers. Pointers are not changed
214 | // by this option. If pointers and references are to be aligned the same, use the
215 | // previous `align-pointer` option. The option align-reference=none will not change
216 | // the reference alignment. The other options are the same as for `align-pointer`.
217 | // In the case of a reference to a pointer (*&) with conflicting alignments, the
218 | // `align-pointer` value will be used.
219 | // can be one of "none", "type", "middle", "name", or null for default.
220 | "align-reference": null,
221 |
222 | // === Formatting Options ===
223 |
224 | // When `style` is "attach", "linux" or "stroustrup", this breaks closing headers
225 | // (e.g. 'else', 'catch', ...) from their immediately preceding closing brackets.
226 | // Closing header brackets are always broken with broken brackets, horstmann
227 | // rackets, indented blocks, and indented brackets.
228 | "break-closing-brackets": false,
229 |
230 | // Break "else if" header combinations into separate lines. This option has no effect
231 | // if keep-one-line-statements is used, the "else if" statements will remain as they are.
232 | // If this option is NOT used, "else if" header combinations will be placed on a single line.
233 | "break-elseifs": false,
234 |
235 | // Add brackets to unbracketed one line conditional statements (e.g. 'if', 'for', 'while'...).
236 | // The statement must be on a single line. The brackets will be added according to the
237 | // currently requested predefined style or bracket type. If no style or bracket type is
238 | // requested the brackets will be attached. If `add-one-line-brackets` is also used the
239 | // result will be one line brackets.
240 | "add-brackets": false,
241 |
242 | // Add one line brackets to unbracketed one line conditional statements
243 | // (e.g. 'if', 'for', 'while'...). The statement must be on a single line.
244 | // The option implies `keep-one-line-blocks` and will not break the one line blocks.
245 | "add-one-line-brackets": false,
246 |
247 | // Remove brackets from conditional statements (e.g. 'if', 'for', 'while'...).
248 | // The statement must be a single statement on a single line. If
249 | // --add-brackets or --add-one-line-brackets is also used the result will
250 | // be to add brackets. Brackets will not be removed from
251 | // "One True Brace Style", --style=1tbs.
252 | "remove-brackets": false,
253 |
254 | // Don't break one-line blocks.
255 | "keep-one-line-blocks": true,
256 |
257 | // Don't break complex statements and multiple statements residing on a single line.
258 | "keep-one-line-statements": true,
259 |
260 | // Closes whitespace in the angle brackets of template definitions. Closing
261 | // the ending angle brackets is now allowed by the C++11 standard.
262 | // Be sure your compiler supports this before making the changes.
263 | "close-templates": false,
264 |
265 | // Remove the preceding '*' in a multi-line comment that begins a line.
266 | // A trailing '*', if present, is also removed. Text that is less than one
267 | // is indent is indented to one indent. Text greater than one indent is
268 | // not changed. Multi-line comments that begin a line but without the
269 | // preceding '*' are indented to one indent for consistency. This can
270 | // slightly modify the indentation of commented out blocks of code.
271 | // Lines containing all '*' are left unchanged. Extra spacing is removed
272 | // from the comment close '*/'.
273 | "remove-comment-prefix": false,
274 |
275 | // The option max-code-length will break a line if the code exceeds # characters.
276 | // The valid values are 50 thru 200. Lines without logical conditionals will
277 | // break on a logical conditional (||, &&, ...), comma, paren, semicolon, or space.
278 | //
279 | // Some code will not be broken, such as comments, quotes, and arrays.
280 | // If used with keep-one-line-blocks or add-one-line-brackets the blocks
281 | // will NOT be broken. If used with keep-one-line-statements the statements
282 | // will be broken at a semicolon if the line goes over the maximum length.
283 | // If there is no available break point within the max code length, the
284 | // line will be broken at the first available break point after the max code length.
285 | //
286 | // By default logical conditionals will be placed first on the new line.
287 | // The option break-after-logical will cause the logical conditionals to be
288 | // placed last on the previous line. This option has no effect without max-code-length.
289 | "max-code-length": -1,
290 | "break-after-logical": false,
291 |
292 | // == Objective-C Options ==
293 | // Because of the longer indents sometimes needed for Objective-C, the option
294 | // "max-instatement-indent" may need to be increased. If you are not getting
295 | // the paren and square bracket alignment you want, try increasing this value.
296 |
297 | // Align the colons in Objective-C method declarations. This option is effective
298 | // for Objective-C files only.
299 | "align-method-colon": false,
300 |
301 | // Insert space padding after the '-' or '+' Objective-C method prefix. This will
302 | // add exactly one space. Any additional spaces will be deleted. This option is
303 | // effective for Objective-C files only.
304 | "pad-method-prefix": false,
305 |
306 | // Remove all space padding after the '-' or '+' Objective-C method prefix.
307 | // If used with pad-method-prefix, this option will be ignored. This option
308 | // is effective for Objective-C files only.
309 | "unpad-method-prefix": false,
310 |
311 | // Add or remove space padding before or after the colons in an Objective-C
312 | // method call. These options will pad exactly one space. Any additional
313 | // spaces will be deleted. Colons immediarely preceeding a paren will not
314 | // be padded. This option is effective for Objective-C files only.
315 | //
316 | // Can be one of "none", "all", "after" or "before", or null for default.
317 | "pad-method-colon": null
318 | },
319 |
320 | //
321 | // Language Specific Options
322 | //
323 | // You can override default options in language-specific options here.
324 | // Addtional options are also provided:
325 | //
326 | // "additional_options": Addtional options following astyle options file style
327 | // (http://astyle.sourceforge.net/astyle.html).
328 | // e.g.: "additional_options": ["--indent=spaces=2", "--convert-tabs"]
329 | //
330 | // "additional_options_file": Addtional options file for astyle (aka astylerc), you must specify
331 | // a full path for that file, environment variable may be included.
332 | // e.g.: "additional_options_file": "%USERPROFILE%/astylerc" // (Windows)
333 | // e.g.: "additional_options_file": "$HOME/.astylerc" // (Linux)
334 | // === Additional variables that you may use ===
335 | // $file_path The directory of the current file, e.g., C:\Files.
336 | // $file The full path to the current file, e.g., C:\Files\Chapter1.txt.
337 | // $file_name The name portion of the current file, e.g., Chapter1.txt.
338 | // $file_extension The extension portion of the current file, e.g., txt.
339 | // $file_base_name The name-only portion of the current file, e.g., Document.
340 | // $packages The full path to the Packages folder.
341 | // The following requires "Sublime Text 3"
342 | // $project The full path to the current project file.
343 | // $project_path The directory of the current project file.
344 | // $project_name The name portion of the current project file.
345 | // $project_extension The extension portion of the current project file.
346 | // $project_base_name The name-only portion of the current project file.
347 | //
348 | // "use_only_additional_options": While true, SublimeAStyleFormatter will *only* process
349 | // options in *both* "additional_options" and "additional_options_file".
350 |
351 |
352 | // Language-specific options for C
353 | "options_c": {
354 | "use_only_additional_options": false,
355 | "additional_options_file": "",
356 | "additional_options": []
357 | },
358 |
359 | // Language-specific for C++
360 | "options_c++": {
361 | "use_only_additional_options": false,
362 | "additional_options_file": "",
363 | "additional_options": []
364 | },
365 |
366 | // Language-specific for Java
367 | "options_java": {
368 | "style": "java",
369 | "use_only_additional_options": false,
370 | "additional_options_file": "",
371 | "additional_options": []
372 | },
373 |
374 | // Language-specific for C#
375 | "options_cs": {
376 | "use_only_additional_options": false,
377 | "additional_options_file": "",
378 | "additional_options": []
379 | }
380 | }
381 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | global:
3 | PACKAGE: "SublimeAStyleFormatter"
4 | matrix:
5 | - SUBLIME_TEXT_VERSION: "2"
6 | - SUBLIME_TEXT_VERSION: "3"
7 |
8 | install:
9 | - ps: Start-FileDownload "https://raw.githubusercontent.com/randy3k/UnitTesting/master/sbin/appveyor.ps1"
10 | - ps: .\appveyor.ps1 "bootstrap" -verbose
11 |
12 | build: off
13 |
14 | test_script:
15 | - ps: .\appveyor.ps1 "run_tests" -verbose
16 |
--------------------------------------------------------------------------------
/dump_default_options:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | Created on 20/01/2011
5 |
6 | v0.1 (C) Gerald Storer
7 | MIT License
8 |
9 | Based on JSON.minify.js:
10 | https://github.com/getify/JSON.minify
11 | '''
12 |
13 | import re
14 |
15 |
16 | def json_minify(json, strip_space=True):
17 | tokenizer = re.compile('"|(/\*)|(\*/)|(//)|\n|\r')
18 | in_string = False
19 | in_multiline_comment = False
20 | in_singleline_comment = False
21 |
22 | new_str = []
23 | from_index = 0 # from is a keyword in Python
24 |
25 | for match in re.finditer(tokenizer, json):
26 |
27 | if not in_multiline_comment and not in_singleline_comment:
28 | tmp2 = json[from_index:match.start()]
29 | if not in_string and strip_space:
30 | tmp2 = re.sub('[ \t\n\r]*', '', tmp2) # replace only white space defined in standard
31 | new_str.append(tmp2)
32 |
33 | from_index = match.end()
34 |
35 | if match.group() == '"' and not in_multiline_comment and not in_singleline_comment:
36 | escaped = re.search('(\\\\)*$', json[:match.start()])
37 | if not in_string or escaped is None or len(escaped.group()) % 2 == 0:
38 | # start of string with ", or unescaped " character found to end string
39 | in_string = not in_string
40 | from_index -= 1 # include " character in next catch
41 |
42 | elif match.group() == '/*' and not in_string and not in_multiline_comment and not in_singleline_comment:
43 | in_multiline_comment = True
44 | elif match.group() == '*/' and not in_string and in_multiline_comment and not in_singleline_comment:
45 | in_multiline_comment = False
46 | elif match.group() == '//' and not in_string and not in_multiline_comment and not in_singleline_comment:
47 | in_singleline_comment = True
48 | elif (match.group() == '\n' or match.group() == '\r') and not in_string and not in_multiline_comment and in_singleline_comment:
49 | in_singleline_comment = False
50 | elif not in_multiline_comment and not in_singleline_comment and (
51 | match.group() not in ['\n', '\r', ' ', '\t'] or not strip_space):
52 | new_str.append(match.group())
53 |
54 | new_str.append(json[from_index:])
55 | return ''.join(new_str)
56 |
57 |
58 | import json
59 | s = json_minify(open('SublimeAStyleFormatter.sublime-settings').read())
60 | json_obj = json.loads(s)
61 | options_default_s = json.dumps(json_obj['options_default'], indent=4, separators=(',', ': '), sort_keys=True)
62 | open('options_default.json', 'w').write(options_default_s)
63 |
--------------------------------------------------------------------------------
/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "install": "messages/install.txt",
3 | "1.3": "messages/1.3.txt",
4 | "1.7": "messages/1.7.txt",
5 | "1.7.1": "messages/1.7.1.txt",
6 | "1.7.3": "messages/1.7.3.txt",
7 | "1.8": "messages/1.8.txt",
8 | "1.9": "messages/1.9.txt",
9 | "1.9.1": "messages/1.9.1.txt",
10 | "1.9.2": "messages/1.9.2.txt",
11 | "1.9.4": "messages/1.9.4.txt",
12 | "2.0.0": "messages/2.0.0.txt",
13 | "2.0.2": "messages/2.0.2.txt",
14 | "2.0.3": "messages/2.0.3.txt",
15 | "2.0.4": "messages/2.0.4.txt",
16 | "2.0.5": "messages/2.0.5.txt",
17 | "2.1.0": "messages/2.1.0.txt",
18 | "3.0.0": "messages/3.0.0.txt",
19 | "3.1.0": "messages/3.1.0.txt",
20 | "3.1.1": "messages/3.1.1.txt"
21 | }
22 |
--------------------------------------------------------------------------------
/messages/1.3.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.3 Changelog:
2 |
3 | New Features
4 | - Add support for formatting current selections:
5 | Bring up Command Palette, select "SublimeAStyleFormatter Format Current Selection";
6 | The default key binding for formatting selections is:
7 | * "ctrl+k, ctrl+f" (Windows/Linux)
8 | * "super+k, super+f" (OSX)
9 |
--------------------------------------------------------------------------------
/messages/1.7.1.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.7.1 Changelog:
2 |
3 | NOTE:
4 | The default keybinding for OSX is changed from `super+alt+f` to `ctrl+alt+f`.
5 |
6 | * Change default keybinding for OSX (was conflict with "replace" in Sublime Text 2).
7 |
--------------------------------------------------------------------------------
/messages/1.7.3.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.7.3 Changelog:
2 |
3 | * Fix a conflict with SublimeCodeIntel.
4 |
--------------------------------------------------------------------------------
/messages/1.7.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.7 Changelog:
2 |
3 | * Buffer will not scroll up and down during formatting now.
4 |
--------------------------------------------------------------------------------
/messages/1.8.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.8 Changelog:
2 |
3 | * Add auto format on file save (option "autoformat_on_save").
4 | * Add context and side bar commands.
5 |
--------------------------------------------------------------------------------
/messages/1.9.1.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.9.1 Changelog:
2 |
3 | * Add Linux support (Both x86 and x86_64) for Sublime Text 3.
4 |
--------------------------------------------------------------------------------
/messages/1.9.2.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.9.2 Changelog:
2 |
3 | * Add OS X support for Sublime Text 3.
4 |
--------------------------------------------------------------------------------
/messages/1.9.4.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.9.4 Changelog:
2 |
3 | * Add OpenCL and Cuda-C++ (each require its syntax file installed) support.
4 |
--------------------------------------------------------------------------------
/messages/1.9.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 1.9 Changelog:
2 |
3 | * Preliminary support for Sublime Text 3.
4 |
--------------------------------------------------------------------------------
/messages/2.0.0.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 2.0.0 Changelog:
2 |
3 | * Update Artistic Style to v2.03 release ([News](http://astyle.sourceforge.net/news.html)
4 | and [Release Notes](http://astyle.sourceforge.net/notes.html)).
5 | * Please note that deprecated bracket options are now removed from astyle v2.03, use
6 | `style` options instead if you have any those deprecated options (usually in your `astylerc` files).
7 | * Add new options: `pad-first-paren-out`, `close-templates`, `max-code-length` and `break-after-logical`.
8 |
--------------------------------------------------------------------------------
/messages/2.0.2.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 2.0.2 Changelog:
2 |
3 |
4 | * Less error-prone default options overriding (You don't need to duplicate whole
5 | `options_defaut` section before customizing now, default options in `options_defaut`
6 | section will be retrieved automatically).
7 |
--------------------------------------------------------------------------------
/messages/2.0.3.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 2.0.3 Changelog:
2 |
3 |
4 | * Add Arduino files support.
5 |
--------------------------------------------------------------------------------
/messages/2.0.4.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 2.0.4 Changelog:
2 |
3 |
4 | * Fix OSX and Linux pyastyle binaries.
5 |
--------------------------------------------------------------------------------
/messages/2.0.5.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 2.0.5 Changelog:
2 |
3 |
4 | * Fix plugin stop working while `additional_options` missing from user options.
5 | * Add `apex` syntax support.
6 | * Add new option: `user_defined_syntax_mode_mapping`.
7 |
--------------------------------------------------------------------------------
/messages/2.1.0.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 2.1.0 Changelog:
2 |
3 |
4 | * Upgrade astyle binary to v2.04.
5 | * Fix unfunctional `user_defined_syntax_mode_mapping` option.
6 | * Fix wrong user keymap setting file location.
7 |
--------------------------------------------------------------------------------
/messages/3.0.0.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 3.0.0 Changelog:
2 |
3 |
4 | NOTE: A Sublime Text restart may be required upon this upgrade.
5 | NOTE: Since "ansi" style is removed, you have to update your settings.
6 | NOTE: Since current version will validate your settings, if anything is broken, you may want to bring up the console to see why.
7 |
8 |
9 | * Upgrade astyle binary to v2.05.1.
10 | * Improper configuration settings will raise errors now.
11 | * Fix missing "google" style option.
12 | * Remove "ansi" style option because it's deprecated in astyle v2.05.
13 | * Remove "indent-preprocessor" option because it was deprecated in astyle v2.04.
14 | * Add "indent-preproc-block" option (introduced in astyle v2.05).
15 | * Add more expanded variables for reaching astylerc file (see `SublimeAStyleFormatter.sublime-settings` for more details, in `additional_options_file`).
16 |
--------------------------------------------------------------------------------
/messages/3.1.0.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 3.1.0 Changelog:
2 |
3 |
4 | **HINT:** A Sublime Text restart may be required upon this upgrade.
5 |
6 | *************************************************
7 | **NOTE: PLEASE READ FOLLOWING CHANGES CAREFULLY**
8 | *************************************************
9 |
10 |
11 | * The following default options were changed, according to *Artistic Style* defaults:
12 | * `indent-preproc-define`: `true` -> `false`
13 | * `indent-col1-comments`: `true` -> `false`
14 | * `align-pointer`: `"name"` -> "not set" (Use *Artistic Style* defaults, which is "no change")
15 | * `align-reference`: `"name"` -> "not set" (Use *Artistic Style* defaults, which is "same as `align-pointer`")
16 | * `keep-one-line-blocks`: `false` -> `true`
17 | * Now an output panel with user-friendly error message will show if anything goes wrong.
18 | * Remove `convert-tabs` option, because it's duplicated with Sublime Text's `translate_tabs_to_spaces` setting.
19 | * Fix a NoneType error while formatting unsaved files.
20 |
--------------------------------------------------------------------------------
/messages/3.1.1.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 3.1.1 Changelog:
2 |
3 |
4 | * Hot-Fix for mysterious ValueError(), introduced in v3.1.0.
5 |
--------------------------------------------------------------------------------
/messages/3.1.2.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter 3.1.2 Changelog:
2 |
3 | ************************* NOTE *************************
4 | A Sublime Text restart may be required upon this upgrade.
5 | **********************************************************
6 |
7 | * Add user friendly error messages for invalid options.
8 | * Strip invalid options in astylerc.
9 |
--------------------------------------------------------------------------------
/messages/install.txt:
--------------------------------------------------------------------------------
1 | SublimeAStyleFormatter
2 | ======================
3 |
4 | SublimeAStyleFormatter is a simple code formatter plugin for Sublime Text 2.
5 | It provides ability to format C, C++, C#, and Java files.
6 |
7 | Usage
8 | -----
9 | ### Key Bindings
10 | The default key bindings for this plugin:
11 |
12 | **Windows, Linux:**
13 | + `ctrl+alt+f`: Format current file
14 | + `ctrl+k, ctrl+f`: Format current selection
15 |
16 | **OSX:**
17 | + `ctrl+alt+f`: Format current file
18 | + `super+k,super+f`: Format current selection
19 |
20 | ### Command Palette
21 | Open the command palette, it apperas as `SublimeAStyleFormatter: Format Current File`
22 | and `SublimeAStyleFormatter Format Current Selection`.
23 |
--------------------------------------------------------------------------------
/options_default.json:
--------------------------------------------------------------------------------
1 | {
2 | "add-brackets": false,
3 | "add-one-line-brackets": false,
4 | "align-method-colon": false,
5 | "align-pointer": null,
6 | "align-reference": null,
7 | "attach-classes": false,
8 | "attach-extern-c": false,
9 | "attach-inlines": false,
10 | "attach-namespaces": false,
11 | "break-after-logical": false,
12 | "break-blocks": null,
13 | "break-closing-brackets": false,
14 | "break-elseifs": false,
15 | "close-templates": false,
16 | "delete-empty-lines": false,
17 | "fill-empty-lines": false,
18 | "indent": null,
19 | "indent-cases": false,
20 | "indent-classes": false,
21 | "indent-col1-comments": false,
22 | "indent-labels": false,
23 | "indent-modifiers": false,
24 | "indent-namespaces": false,
25 | "indent-preproc-block": false,
26 | "indent-preproc-cond": false,
27 | "indent-preproc-define": false,
28 | "indent-spaces": null,
29 | "indent-switches": false,
30 | "keep-one-line-blocks": true,
31 | "keep-one-line-statements": true,
32 | "max-code-length": -1,
33 | "max-instatement-indent": 40,
34 | "min-conditional-indent": 2,
35 | "pad-first-paren-out": false,
36 | "pad-header": true,
37 | "pad-method-colon": null,
38 | "pad-method-prefix": false,
39 | "pad-oper": true,
40 | "pad-paren": false,
41 | "pad-paren-in": false,
42 | "pad-paren-out": false,
43 | "remove-brackets": false,
44 | "remove-comment-prefix": false,
45 | "style": null,
46 | "unpad-method-prefix": false,
47 | "unpad-paren": false
48 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repo": "SublimeAStyleFormatter",
3 | "name": "SublimeAStyleFormatter",
4 | "description": "C/C++/C#/Java code formatter/beautifier with AStyle.",
5 | "author": "timonwong",
6 | "homepage": "http://timonwong.github.io/SublimeAStyleFormatter/",
7 | "details": "https://github.com/timonwong/SublimeAStyleFormatter",
8 | "platforms": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/pyastyle/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | if sys.version_info < (3, 0):
4 | from .python2 import *
5 | else:
6 | from .python3 import *
7 |
--------------------------------------------------------------------------------
/pyastyle/python2/__init__.py:
--------------------------------------------------------------------------------
1 | try:
2 | from ._local_arch.pyastyle import *
3 | platform = "Local arch"
4 | except ImportError:
5 | try:
6 | from ._linux_x86_64.pyastyle import *
7 | platform = "Linux 64 bits"
8 | except ImportError:
9 | try:
10 | from ._linux_x86.pyastyle import *
11 | platform = "Linux 32 bits"
12 | except ImportError:
13 | try:
14 | from ._win64.pyastyle import *
15 | platform = "Windows 64 bits"
16 | except ImportError:
17 | try:
18 | from ._win32.pyastyle import *
19 | platform = "Windows 32 bits"
20 | except ImportError:
21 | try:
22 | from ._macosx_universal.pyastyle import *
23 | platform = "MacOS X Universal"
24 | except ImportError:
25 | raise ImportError("Could not find a suitable pyastyle binary for your platform and architecture.")
26 |
--------------------------------------------------------------------------------
/pyastyle/python2/_linux_x86/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_linux_x86/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python2/_linux_x86/pyastyle.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_linux_x86/pyastyle.so
--------------------------------------------------------------------------------
/pyastyle/python2/_linux_x86_64/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_linux_x86_64/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python2/_linux_x86_64/pyastyle.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_linux_x86_64/pyastyle.so
--------------------------------------------------------------------------------
/pyastyle/python2/_local_arch/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_local_arch/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python2/_macosx_universal/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_macosx_universal/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python2/_macosx_universal/pyastyle.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_macosx_universal/pyastyle.so
--------------------------------------------------------------------------------
/pyastyle/python2/_win32/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_win32/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python2/_win32/pyastyle.pyd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_win32/pyastyle.pyd
--------------------------------------------------------------------------------
/pyastyle/python2/_win64/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_win64/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python2/_win64/pyastyle.pyd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python2/_win64/pyastyle.pyd
--------------------------------------------------------------------------------
/pyastyle/python3/__init__.py:
--------------------------------------------------------------------------------
1 | try:
2 | from ._local_arch.pyastyle import *
3 | platform = "Local arch"
4 | except ImportError:
5 | try:
6 | from ._linux_x86_64.pyastyle import *
7 | platform = "Linux 64 bits"
8 | except ImportError:
9 | try:
10 | from ._linux_x86.pyastyle import *
11 | platform = "Linux 32 bits"
12 | except ImportError:
13 | try:
14 | from ._win64.pyastyle import *
15 | platform = "Windows 64 bits"
16 | except ImportError:
17 | try:
18 | from ._win32.pyastyle import *
19 | platform = "Windows 32 bits"
20 | except ImportError:
21 | try:
22 | from ._macosx_universal.pyastyle import *
23 | platform = "MacOS X Universal"
24 | except ImportError:
25 | raise ImportError("Could not find a suitable pyastyle binary for your platform and architecture.")
26 |
--------------------------------------------------------------------------------
/pyastyle/python3/_linux_x86/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_linux_x86/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python3/_linux_x86/pyastyle.cpython-33m.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_linux_x86/pyastyle.cpython-33m.so
--------------------------------------------------------------------------------
/pyastyle/python3/_linux_x86_64/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_linux_x86_64/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python3/_linux_x86_64/pyastyle.cpython-33m.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_linux_x86_64/pyastyle.cpython-33m.so
--------------------------------------------------------------------------------
/pyastyle/python3/_local_arch/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_local_arch/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python3/_macosx_universal/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_macosx_universal/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python3/_macosx_universal/pyastyle.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_macosx_universal/pyastyle.so
--------------------------------------------------------------------------------
/pyastyle/python3/_win32/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_win32/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python3/_win32/pyastyle.pyd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_win32/pyastyle.pyd
--------------------------------------------------------------------------------
/pyastyle/python3/_win64/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_win64/__init__.py
--------------------------------------------------------------------------------
/pyastyle/python3/_win64/pyastyle.pyd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timonwong/SublimeAStyleFormatter/0ac8284db12911cabf7ab5004ba3eb99b96ed1c9/pyastyle/python3/_win64/pyastyle.pyd
--------------------------------------------------------------------------------
/src/Dependencies.txt:
--------------------------------------------------------------------------------
1 | gcc and g++
2 |
3 | == Sublime Text 2 Compilation ==
4 | python 2.6
5 | python 2.6 dev headers and libraries
6 |
7 | == Sublime Text 3 Complication ==
8 | python 3.3
9 | python 3.3 dev headers and libraries
10 |
--------------------------------------------------------------------------------
/src/_build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Absolute path to this script, e.g. /home/user/bin/foo.sh
4 | SCRIPT=$(readlink "$0")
5 | # Absolute path this script is in, thus /home/user/bin
6 | SCRIPTPATH=$(dirname "${SCRIPT}")
7 |
8 | # Parsing arguments
9 | is_user_build=1
10 | while [[ -n "$1" ]]; do
11 | case "$1" in
12 | --python=*)
13 | python_bin="${1#*=}"
14 | ;;
15 | --dist)
16 | is_user_build=
17 | ;;
18 | esac
19 | shift
20 | done
21 |
22 | if [[ -z "${python_bin}" ]]; then
23 | echo "Usage: $0 --python=/path/to/python"
24 | exit 1
25 | fi
26 |
27 | python_ver=$(${python_bin} -c 'import sys; print(sys.version[0])')
28 |
29 | reset() {
30 | pushd "${SCRIPTPATH}" > /dev/null
31 | rm -rf pyastyle/build
32 | popd > /dev/null
33 | }
34 |
35 | reset
36 |
37 | if [[ "${OSTYPE}" = "linux-gnu" ]]; then
38 | echo "Linux build!"
39 |
40 | if [[ $(uname -m) == 'x86_64' ]]; then
41 | pkg_folder="_linux_x86_64"
42 |
43 | CXXFLAGS="-fPIC ${CXXFLAGS}"
44 | CFLAGS="-fPIC ${CFLAGS}"
45 | else
46 | pkg_folder="_linux_x86"
47 |
48 | CXXFLAGS="${CFLAGS}"
49 | CFLAGS="${CFLAGS}"
50 | fi
51 |
52 | # In Linux, Sublime Text's Python is compiled with UCS4:
53 | if [[ "${python_ver}" == "2" ]]; then
54 | CFLAGS="-DPy_UNICODE_SIZE=4 ${CFLAGS}"
55 | CXXFLAGS="-DPy_UNICODE_SIZE=4 ${CXXFLAGS}"
56 | fi
57 | elif [[ "${OSTYPE:0:6}" == "darwin" ]]; then
58 | echo "Mac OS X build!"
59 |
60 | if [[ "${python_ver}" == "2" ]]; then
61 | pkg_folder="_macosx_universal"
62 | arch_flags="-arch i386 -arch x86_64"
63 | osxver_flags="-stdlib=libstdc++ -mmacosx-version-min=10.6"
64 | else
65 | pkg_folder="_macosx_universal"
66 | arch_flags="-arch x86_64"
67 | osxver_flags="-stdlib=libstdc++ -mmacosx-version-min=10.7"
68 | fi
69 |
70 | ARCHFLAGS="${arch_flags} ${ARCHFLAGS}"
71 | CXXFLAGS="${arch_flags} ${osxver_flags} ${CXXFLAGS}"
72 | CFLAGS="${arch_flags} ${osxver_flags} ${CFLAGS}"
73 | LDFLAGS="${arch_flags} ${osxver_flags} ${LDFLAGS}"
74 | else
75 | echo "Unknown system!"
76 | exit 1
77 | fi
78 |
79 | export CXXFLAGS
80 | export CFLAGS
81 | export CXXFLAGS
82 | export LDFLAGS
83 |
84 | # Is user build?
85 | if [[ -n "${is_user_build}" ]]; then
86 | pkg_folder="_local_arch"
87 | fi
88 |
89 | target_folder="../pyastyle/python${python_ver}/${pkg_folder}"
90 |
91 | (echo "Building pyastyle..." && \
92 | cd "${SCRIPTPATH}/pyastyle" && \
93 | ${python_bin} setup.py build && \
94 | cd "${SCRIPTPATH}"
95 | ) && \
96 | mkdir -p "${target_folder}" && \
97 | echo "Copying binary to ${target_folder}..." && \
98 | find "${SCRIPTPATH}/pyastyle/build" -type f '(' -name "pyastyle.so" -o -name "pyastyle.*.so" ')' -exec cp {} "${target_folder}" \; && \
99 |
100 | reset && \
101 | echo "Done!" || \
102 | echo "Build Failed!${ERR}"
103 |
104 | strip "${target_folder}"/*.so > /dev/null 2>&1
105 |
--------------------------------------------------------------------------------
/src/user_build_py2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Absolute path to this script, e.g. /home/user/bin/foo.sh
4 | SCRIPT=$(readlink "$0")
5 | # Absolute path this script is in, thus /home/user/bin
6 | SCRIPTPATH=$(dirname "${SCRIPT}")
7 |
8 | /bin/bash "${SCRIPTPATH}/_build.sh" --python="${PYTHON:-python2.6}" "$@"
9 |
--------------------------------------------------------------------------------
/src/user_build_py3.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Absolute path to this script, e.g. /home/user/bin/foo.sh
4 | SCRIPT=$(readlink "$0")
5 | # Absolute path this script is in, thus /home/user/bin
6 | SCRIPTPATH=$(dirname "${SCRIPT}")
7 |
8 | /bin/bash "${SCRIPTPATH}/_build.sh" --python="${PYTHON:-python3.3}" "$@"
9 |
--------------------------------------------------------------------------------
/src/win_build_all.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | :: Windows SDK for Windows 7 (7.0) should be installed first.
3 | :: And the "%VS90COMNTOOLS%" enviornment variable should be set correctly.
4 | :: NOTE: For Python 3.3, MSVC 10 is required for building.
5 |
6 | :: Configuration
7 | set PYTHON26_X86="F:\Langs\Python26\python.exe"
8 | set PYTHON26_X64="F:\Langs\Python26_x64\python.exe"
9 | set PYTHON33_X86="F:\Langs\Python33\python.exe"
10 | set PYTHON33_X64="F:\Langs\Python33_x64\python.exe"
11 |
12 | :: Clean
13 | :: if exist pyastyle\build rmdir /s /q pyastyle\build > NUL
14 |
15 | call :buildExtension %PYTHON26_X86%
16 | call :buildExtension %PYTHON26_X64%
17 | call :buildExtension %PYTHON33_X86%
18 | call :buildExtension %PYTHON33_X64%
19 | goto done
20 |
21 | :buildExtension
22 |
23 | setlocal
24 |
25 | if not exist %~1 (
26 | echo Python executable "%~1" cannot be found.
27 | goto buildExtensionReturn
28 | )
29 |
30 | set PY_PRINT_VERSION=%~1 -c "import sys;print('.'.join(map(lambda x: str(x), sys.version_info[:2])))"
31 | set PY_PRINT_32OR64=%~1 -c "import struct;print(8 * struct.calcsize('P'))"
32 | for /f "tokens=*" %%i in ('%PY_PRINT_VERSION%') do set PY_VERSION=%%i
33 | for /f "tokens=*" %%i in ('%PY_PRINT_32OR64%') do set PY_32OR64=%%i
34 |
35 | if "%PY_32OR64%" == "32" (
36 | set PY_BUILD_ARCH=32
37 | )
38 |
39 | if "%PY_32OR64%" == "64" (
40 | set PY_BUILD_ARCH=-amd64
41 | )
42 |
43 | set PY_VERSION_OK=0
44 | set MSSdk=
45 | set DISTUTILS_USE_SDK=
46 |
47 | if "%PY_VERSION%" == "2.6" (
48 | set PY_VERSION_OK=1
49 | set PY_VERSION_MAJOR=2
50 |
51 | set MSSdk=1
52 | set DISTUTILS_USE_SDK=1
53 |
54 | if "%PY_32OR64%" == "32" (
55 | call "%VS90COMNTOOLS%\vcvars32.bat"
56 | )
57 |
58 | if "%PY_32OR64%" == "64" (
59 | call "%VS90COMNTOOLS%\vcvars64.bat"
60 | )
61 | )
62 |
63 | if "%PY_VERSION%" == "3.3" (
64 | set PY_VERSION_OK=1
65 | set PY_VERSION_MAJOR=3
66 | )
67 |
68 | if "%PY_VERSION_OK%" == "0" (
69 | echo "Invalid python version: %PY_VERSION%"
70 | goto buildExtensionReturn
71 | )
72 |
73 | set OUTPUT_FILE=pyastyle\build\lib.win%PY_BUILD_ARCH%-%PY_VERSION%\pyastyle.pyd
74 | set TARGET_DIR=..\pyastyle\python%PY_VERSION_MAJOR%\_win%PY_32OR64%\
75 |
76 | cd pyastyle
77 | :: Compilation
78 | %~1 setup.py build --compiler=msvc
79 | cd ..
80 |
81 | if not exist %TARGET_DIR% mkdir %TARGET_DIR%
82 | copy /y %OUTPUT_FILE% %TARGET_DIR%
83 | echo Finished building extension for Python %PY_VERSION% (%PY_32OR64%bit)
84 | echo.
85 |
86 | :buildExtensionReturn
87 | endlocal
88 | goto :eof
89 |
90 | :done
91 |
--------------------------------------------------------------------------------
/tests/test.py:
--------------------------------------------------------------------------------
1 | import json
2 | import sublime
3 | import sys
4 | import os
5 |
6 | from unittest import TestCase
7 |
8 | IS_ST2 = sublime.version() < '3000'
9 | IS_ST3 = not IS_ST2
10 |
11 |
12 | if IS_ST2:
13 | plugin = sys.modules["AStyleFormat"]
14 | else:
15 | plugin = sys.modules["SublimeAStyleFormatter.AStyleFormat"]
16 |
17 |
18 | plugin_default_options = {}
19 |
20 |
21 | def _load_default_options():
22 | global plugin_default_options
23 |
24 | with open(os.path.join(plugin.__path__, 'options_default.json')) as f:
25 | plugin_default_options = {
26 | 'debug': False,
27 | 'autoformat_on_save': False,
28 | 'user_defined_syntax_mode_mapping': {},
29 | 'options_c': {
30 | 'use_only_additional_options': False,
31 | 'additional_options_file': "",
32 | 'additional_options': []
33 | },
34 | 'options_c++': {
35 | 'use_only_additional_options': False,
36 | 'additional_options_file': "",
37 | 'additional_options': []
38 | },
39 | 'options_default': json.loads(f.read())
40 | }
41 | _load_default_options()
42 |
43 |
44 | class WithViewTestCaseMixin(object):
45 | def setUp(self):
46 | self.view = sublime.active_window().new_file()
47 | settings = self.view.settings()
48 | settings.set('_UNDER_UNITTEST', True)
49 | # Use default astyle formatter in every tests
50 | settings.set('AStyleFormatter', plugin_default_options)
51 |
52 | def tearDown(self):
53 | self.view.set_scratch(True)
54 | self.view.window().focus_view(self.view)
55 | self.view.window().run_command("close_file")
56 |
57 | def _insert_text(self, string):
58 | # We should disable auto indent
59 | backup = self.view.settings().get('auto_indent')
60 | try:
61 | self.view.settings().set('auto_indent', False)
62 | self.view.run_command("insert", {"characters": string})
63 | finally:
64 | self.view.settings().set('auto_indent', backup)
65 |
66 | def _get_text(self, region=None):
67 | if not region:
68 | region = sublime.Region(0, self.view.size())
69 | return self.view.substr(region)
70 |
71 | def _set_syntax(self, syntax):
72 | self.view.settings().set("syntax", syntax)
73 |
74 |
75 | class PluginInternalFunctionTests(WithViewTestCaseMixin, TestCase):
76 |
77 | def setUp(self):
78 | super(PluginInternalFunctionTests, self).setUp()
79 | os.environ['_SUBLIME_ASTYLE_FORMATTER_TEST'] = '1'
80 | self.view.settings().set('AStyleFormatter', {'@@debug@@': '@@debug@@'})
81 |
82 | def tearDown(self):
83 | super(PluginInternalFunctionTests, self).tearDown()
84 | del os.environ['_SUBLIME_ASTYLE_FORMATTER_TEST']
85 | self.view.settings().erase('AStyleFormatter')
86 |
87 | def test_custom_expandvars_not_substitutable(self):
88 | expected = '${__NOTVALIDNOTVALID}$__NOTVALIDNOTVALID'
89 | actual = plugin.custom_expandvars(
90 | '${__NOTVALIDNOTVALID}$__NOTVALIDNOTVALID', {})
91 | self.assertEqual(expected, actual)
92 |
93 | def test_custom_expandvars_environ_only(self):
94 | fmt = ('This is a test: ${_SUBLIME_ASTYLE_FORMATTER_TEST}'
95 | '$_SUBLIME_ASTYLE_FORMATTER_TEST')
96 | expected = 'This is a test: 11'
97 | actual = plugin.custom_expandvars(fmt, {})
98 | self.assertEqual(expected, actual)
99 |
100 | def test_custom_expandvars_custom_only(self):
101 | fmt = 'This is a test: ${_custom_custom}$_custom_custom'
102 | expected = 'This is a test: 22'
103 | actual = plugin.custom_expandvars(fmt, {'_custom_custom': '2'})
104 | self.assertEqual(expected, actual)
105 |
106 | def test_custom_expandvars_environ_and_custom(self):
107 | fmt = ('This is a test: $_SUBLIME_ASTYLE_FORMATTER_TEST'
108 | '${_custom_custom}')
109 | expected = 'This is a test: 12'
110 | actual = plugin.custom_expandvars(fmt, {'_custom_custom': '2'})
111 | self.assertEqual(expected, actual)
112 |
113 | def test_get_settings_for_view(self):
114 | # Test default
115 | expected = '_blah_blah_blah'
116 | actual = plugin.get_settings_for_view(self.view, '_blah_blah_blah_key',
117 | default=expected)
118 | self.assertEqual(expected, actual)
119 |
120 | # Test general
121 | expected = False
122 | actual = plugin.get_settings_for_view(self.view, 'autoformat_on_save')
123 | self.assertEqual(expected, actual)
124 |
125 | # Test per-project setting
126 | expected = '@@debug@@'
127 | actual = plugin.get_settings_for_view(self.view, '@@debug@@')
128 | self.assertEqual(expected, actual)
129 |
130 | def test_get_syntax_for_view_plain_text(self):
131 | # By default, plain text
132 | self.assertFalse(plugin.get_syntax_for_view(self.view))
133 |
134 | self._set_syntax('Packages/C++/C.tmLanguage')
135 | self.assertEqual('c', plugin.get_syntax_for_view(self.view))
136 |
137 | self._set_syntax('Packages/C++/C++.tmLanguage')
138 | self.assertEqual('c++', plugin.get_syntax_for_view(self.view))
139 |
140 | self._set_syntax('Packages/Java/Java.tmLanguage')
141 | self.assertEqual('java', plugin.get_syntax_for_view(self.view))
142 |
143 | self._set_syntax('Packages/C#/C#.tmLanguage')
144 | self.assertEqual('cs', plugin.get_syntax_for_view(self.view))
145 |
146 | def test_is_supported_syntax(self):
147 | actual = list(map(lambda syntax: plugin.is_supported_syntax(self.view,
148 | syntax),
149 | ['c', 'c++', 'cs', 'java']))
150 | expected = [True] * len(actual)
151 | self.assertEqual(expected, actual)
152 |
153 |
154 | class AstyleformatCommandTests(WithViewTestCaseMixin, TestCase):
155 | def test_do_not_run_if_synatx_not_supported(self):
156 | expected = """\
157 | int main(void) {
158 | int x;
159 | int y;
160 | }"""
161 | self._insert_text(expected)
162 | # Plain text
163 | self.view.run_command('astyleformat')
164 |
165 | self.assertEqual(expected, self._get_text())
166 |
167 | def test_run_with_c_file(self):
168 | self._set_syntax('Packages/C++/C.tmLanguage')
169 | original = """\
170 | int main(void) {
171 | int x;
172 | int y;
173 | }"""
174 | expected = """\
175 | int main(void) {
176 | int x;
177 | int y;
178 | }"""
179 | self._insert_text(original)
180 | self.view.run_command('astyleformat')
181 | self.assertEqual(expected, self._get_text().replace('\t', ' '))
182 |
--------------------------------------------------------------------------------
/unittesting.json:
--------------------------------------------------------------------------------
1 | {
2 | "tests_dir" : "tests",
3 | "async": false,
4 | "deferred": false,
5 | "verbosity": 2
6 | }
7 |
--------------------------------------------------------------------------------