├── .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 | [![Travis-CI Build Status](https://travis-ci.org/timonwong/SublimeAStyleFormatter.svg?branch=master)](https://travis-ci.org/timonwong/SublimeAStyleFormatter) 5 | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/timonwong/SublimeAStyleFormatter?branch=master&svg=true)](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 | [![Donate via PayPal](http://dl.dropbox.com/u/2451120/donate-with-paypal.png)](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 | --------------------------------------------------------------------------------