├── messages.json ├── .gitignore ├── CSS.sublime-settings ├── JSON.sublime-settings ├── Javascript.sublime-settings ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Alignment.sublime-commands ├── messages └── 2.0.0.txt ├── Base File.sublime-settings ├── readme.creole ├── Default.sublime-commands ├── Main.sublime-menu └── Alignment.py /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "2.0.0": "messages/2.0.0.txt" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.sublime-project 3 | *.sublime-workspace -------------------------------------------------------------------------------- /CSS.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "alignment_chars": ["=", ":"] 3 | } -------------------------------------------------------------------------------- /JSON.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "alignment_chars": ["=", ":"] 3 | } -------------------------------------------------------------------------------- /Javascript.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "alignment_chars": ["=", ":"] 3 | } -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+a"], "command": "alignment" } 3 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+ctrl+a"], "command": "alignment" } 3 | ] -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+a"], "command": "alignment" } 3 | ] -------------------------------------------------------------------------------- /Alignment.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "Alignment: Align selected region or whole file", "command": "alignment" } 3 | ] -------------------------------------------------------------------------------- /messages/2.0.0.txt: -------------------------------------------------------------------------------- 1 | Sublime Alignment 2.0.0 Changelog: 2 | - Removed the ctrl+shift+a shortcut on Windows/Linux and the cmd+shift+a 3 | shortcut on OS X to prevent conflict with the shortcut for Expand to Tag. 4 | The plugin is now triggered via ctrl+alt+a on Windows/Linux and ctrl+cmd+a 5 | on OS X. -------------------------------------------------------------------------------- /Base File.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // If the indent level of a multi-line selection should be aligned 3 | "align_indent": true, 4 | 5 | // If indentation is done via tabs, set this to true to also align 6 | // mid-line characters via tabs. This may cause alignment issues when 7 | // viewing the file in an editor with different tab width settings. This 8 | // will also cause multi-character operators to be left-aligned to the 9 | // first character in the operator instead of the character from the 10 | // "alignment_chars" setting. 11 | "mid_line_tabs": false, 12 | 13 | // The mid-line characters to align in a multi-line selection, changing 14 | // this to an empty array will disable mid-line alignment 15 | "alignment_chars": ["="], 16 | 17 | // If the following character is matched for alignment, insert a space 18 | // before it in the final alignment 19 | "alignment_space_chars": ["="], 20 | 21 | // The characters to align along with "alignment_chars" 22 | // For instance if the = is to be aligned, there are a number of 23 | // symbols that can be combined with the = to make an operator, and all 24 | // of those must be kept next to the = for the operator to be parsed 25 | "alignment_prefix_chars": [ 26 | "+", "-", "&", "|", "<", ">", "!", "~", "%", "/", "*", "." 27 | ] 28 | } -------------------------------------------------------------------------------- /readme.creole: -------------------------------------------------------------------------------- 1 | = Sublime Alignment 2 | 3 | A simple key-binding for aligning multi-line and multiple selections in 4 | [[http://sublimetext.com/2|Sublime Text 2]]. 5 | 6 | Please see http://wbond.net/sublime_packages/alignment for install instructions, 7 | screenshots and documentation. 8 | 9 | == License 10 | 11 | All of Sublime Alignment is licensed under the MIT license. 12 | 13 | Copyright (c) 2011 Will Bond 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences: Alignment File Settings – Default", 4 | "command": "open_file", 5 | "args": { 6 | "file": "${packages}/Terminal/Base File.sublime-settings" 7 | } 8 | }, 9 | { 10 | "caption": "Preferences: Alignment File Settings – User", 11 | "command": "open_file", 12 | "args": { 13 | "file": "${packages}/User/Base File.sublime-settings" 14 | } 15 | }, 16 | { 17 | "caption": "Preferences: Alignment File Settings – Syntax Specific – User", 18 | "command": "open_file_settings" 19 | }, 20 | { 21 | "caption": "Preferences: Alignment Key Bindings – Default", 22 | "command": "open_file", 23 | "args": { 24 | "file": "${packages}/Alignment/Default (Windows).sublime-keymap", 25 | "platform": "Windows" 26 | } 27 | }, 28 | { 29 | "caption": "Preferences: Alignment Key Bindings – Default", 30 | "command": "open_file", 31 | "args": { 32 | "file": "${packages}/Alignment/Default (OSX).sublime-keymap", 33 | "platform": "OSX" 34 | } 35 | }, 36 | { 37 | "caption": "Preferences: Alignment Key Bindings – Default", 38 | "command": "open_file", 39 | "args": { 40 | "file": "${packages}/Alignment/Default (Linux).sublime-keymap", 41 | "platform": "Linux" 42 | } 43 | }, 44 | { 45 | "caption": "Preferences: Alignment Key Bindings – User", 46 | "command": "open_file", 47 | "args": { 48 | "file": "${packages}/User/Default (Windows).sublime-keymap", 49 | "platform": "Windows" 50 | } 51 | }, 52 | { 53 | "caption": "Preferences: Alignment Key Bindings – User", 54 | "command": "open_file", 55 | "args": { 56 | "file": "${packages}/User/Default (OSX).sublime-keymap", 57 | "platform": "OSX" 58 | } 59 | }, 60 | { 61 | "caption": "Preferences: Alignment Key Bindings – User", 62 | "command": "open_file", 63 | "args": { 64 | "file": "${packages}/User/Default (Linux).sublime-keymap", 65 | "platform": "Linux" 66 | } 67 | } 68 | ] -------------------------------------------------------------------------------- /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 | "caption": "Alignment", 16 | "children": 17 | [ 18 | { 19 | "command": "open_file", 20 | "args": {"file": "${packages}/Alignment/Base File.sublime-settings"}, 21 | "caption": "Settings – Default" 22 | }, 23 | { 24 | "command": "open_file", 25 | "args": {"file": "${packages}/User/Base File.sublime-settings"}, 26 | "caption": "Settings – User" 27 | }, 28 | { 29 | "command": "open_file_settings", 30 | "caption": "Settings – Syntax Specific – User" 31 | }, 32 | { 33 | "command": "open_file", 34 | "args": { 35 | "file": "${packages}/Alignment/Default (Windows).sublime-keymap", 36 | "platform": "Windows" 37 | }, 38 | "caption": "Key Bindings – Default" 39 | }, 40 | { 41 | "command": "open_file", 42 | "args": { 43 | "file": "${packages}/Alignment/Default (OSX).sublime-keymap", 44 | "platform": "OSX" 45 | }, 46 | "caption": "Key Bindings – Default" 47 | }, 48 | { 49 | "command": "open_file", 50 | "args": { 51 | "file": "${packages}/Alignment/Default (Linux).sublime-keymap", 52 | "platform": "Linux" 53 | }, 54 | "caption": "Key Bindings – Default" 55 | }, 56 | { 57 | "command": "open_file", 58 | "args": { 59 | "file": "${packages}/User/Default (Windows).sublime-keymap", 60 | "platform": "Windows" 61 | }, 62 | "caption": "Key Bindings – User" 63 | }, 64 | { 65 | "command": "open_file", 66 | "args": { 67 | "file": "${packages}/User/Default (OSX).sublime-keymap", 68 | "platform": "OSX" 69 | }, 70 | "caption": "Key Bindings – User" 71 | }, 72 | { 73 | "command": "open_file", 74 | "args": { 75 | "file": "${packages}/User/Default (Linux).sublime-keymap", 76 | "platform": "Linux" 77 | }, 78 | "caption": "Key Bindings – User" 79 | }, 80 | { "caption": "-" } 81 | ] 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /Alignment.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import re 4 | import math 5 | import os 6 | import sys 7 | 8 | # This is necessary due to load order of packages in Sublime Text 2 9 | sys.path.append(os.path.join(sublime.packages_path(), 'Default')) 10 | indentation = __import__('indentation') 11 | reload(indentation) 12 | del sys.path[-1] 13 | 14 | normed_rowcol = indentation.line_and_normed_pt 15 | 16 | 17 | def convert_to_mid_line_tabs(view, edit, tab_size, pt, length): 18 | spaces_end = pt + length 19 | spaces_start = spaces_end 20 | while view.substr(spaces_start-1) == ' ': 21 | spaces_start -= 1 22 | spaces_len = spaces_end - spaces_start 23 | normed_start = normed_rowcol(view, spaces_start)[1] 24 | normed_mod = normed_start % tab_size 25 | tabs_len = 0 26 | diff = 0 27 | if normed_mod != 0: 28 | diff = tab_size - normed_mod 29 | tabs_len += 1 30 | tabs_len += int(math.ceil(float(spaces_len - diff) 31 | / float(tab_size))) 32 | view.replace(edit, sublime.Region(spaces_start, 33 | spaces_end), '\t' * tabs_len) 34 | return tabs_len - spaces_len 35 | 36 | 37 | def blank(line): 38 | return not line.strip() 39 | 40 | 41 | def get_indent_level(line): 42 | indent_level = 0 43 | 44 | for c in line: 45 | if c == ' ' or c == '\t': 46 | indent_level += 1 47 | else: 48 | break 49 | 50 | return indent_level 51 | 52 | 53 | def get_blocks(code): 54 | blocks = [] 55 | new_block = [] 56 | prev_indent_level = 0 57 | for i, line in enumerate(code.split('\n')): 58 | indent_level = get_indent_level(line) 59 | if not blank(line) and (indent_level == prev_indent_level): 60 | new_block.append(i) 61 | else: 62 | if len(new_block) > 0: 63 | blocks.append(new_block) 64 | new_block = [] 65 | 66 | if not blank(line): 67 | new_block = [i] 68 | prev_indent_level = indent_level 69 | 70 | if new_block: 71 | blocks.append(new_block) 72 | 73 | return blocks 74 | 75 | 76 | class AlignmentCommand(sublime_plugin.TextCommand): 77 | 78 | def run(self, edit): 79 | view = self.view 80 | sel = view.sel() 81 | max_col = 0 82 | 83 | settings = view.settings() 84 | tab_size = int(settings.get('tab_size', 8)) 85 | use_spaces = settings.get('translate_tabs_to_spaces') 86 | 87 | def align_lines(line_nums): 88 | trim_trailing_white_space = \ 89 | settings.get('trim_trailing_white_space_on_save') 90 | 91 | if settings.get('align_indent'): 92 | # Align the left edges by first finding the left edge 93 | for row in line_nums: 94 | pt = view.text_point(row, 0) 95 | 96 | # Skip blank lines when the user times trailing whitespace 97 | line = view.line(pt) 98 | if trim_trailing_white_space and line.a == line.b: 99 | continue 100 | 101 | char = view.substr(pt) 102 | while char == ' ' or char == '\t': 103 | # Turn tabs into spaces when the preference is spaces 104 | if use_spaces and char == '\t': 105 | view.replace(edit, sublime.Region(pt, pt + 1), ' ' * 106 | tab_size) 107 | 108 | # Turn spaces into tabs when tabs are the preference 109 | if not use_spaces and char == ' ': 110 | max_pt = pt + tab_size 111 | end_pt = pt 112 | while view.substr(end_pt) == ' ' and end_pt < \ 113 | max_pt: 114 | end_pt += 1 115 | view.replace(edit, sublime.Region(pt, end_pt), 116 | '\t') 117 | 118 | pt += 1 119 | 120 | # Rollback if the left edge wraps to the next line 121 | if view.rowcol(pt)[0] != row: 122 | pt -= 1 123 | break 124 | 125 | char = view.substr(pt) 126 | 127 | points.append(pt) 128 | max_col = max([max_col, view.rowcol(pt)[1]]) 129 | 130 | # Adjust the left edges based on the maximum that was found 131 | adjustment = 0 132 | max_length = 0 133 | for pt in points: 134 | pt += adjustment 135 | length = max_col - view.rowcol(pt)[1] 136 | max_length = max([max_length, length]) 137 | adjustment += length 138 | view.insert(edit, pt, (' ' if use_spaces else '\t') * 139 | length) 140 | 141 | perform_mid_line = max_length == 0 142 | 143 | else: 144 | perform_mid_line = True 145 | 146 | alignment_chars = settings.get('alignment_chars') 147 | if alignment_chars == None: 148 | alignment_chars = [] 149 | alignment_prefix_chars = settings.get('alignment_prefix_chars') 150 | if alignment_prefix_chars == None: 151 | alignment_prefix_chars = [] 152 | alignment_space_chars = settings.get('alignment_space_chars') 153 | if alignment_space_chars == None: 154 | alignment_space_chars = [] 155 | 156 | alignment_pattern = '|'.join([re.escape(ch) for ch in 157 | alignment_chars]) 158 | 159 | if perform_mid_line and alignment_chars: 160 | points = [] 161 | max_col = 0 162 | for row in line_nums: 163 | pt = view.text_point(row, 0) 164 | matching_region = view.find(alignment_pattern, pt) 165 | if not matching_region: 166 | continue 167 | matching_char_pt = matching_region.a 168 | 169 | insert_pt = matching_char_pt 170 | # If the equal sign is part of a multi-character 171 | # operator, bring the first character forward also 172 | if view.substr(insert_pt-1) in alignment_prefix_chars: 173 | insert_pt -= 1 174 | 175 | space_pt = insert_pt 176 | while view.substr(space_pt-1) in [' ', '\t']: 177 | space_pt -= 1 178 | # Replace tabs with spaces for consistent indenting 179 | if view.substr(space_pt) == '\t': 180 | view.replace(edit, sublime.Region(space_pt, 181 | space_pt+1), ' ' * tab_size) 182 | matching_char_pt += tab_size - 1 183 | insert_pt += tab_size - 1 184 | 185 | if view.substr(matching_char_pt) in alignment_space_chars: 186 | space_pt += 1 187 | 188 | # If the next equal sign is not on this line, skip the line 189 | if view.rowcol(matching_char_pt)[0] != row: 190 | continue 191 | 192 | points.append(insert_pt) 193 | max_col = max([max_col, normed_rowcol(view, space_pt)[1]]) 194 | 195 | # The adjustment takes care of correcting point positions 196 | # since spaces are being inserted, which changes the points 197 | adjustment = 0 198 | for pt in points: 199 | pt += adjustment 200 | length = max_col - normed_rowcol(view, pt)[1] 201 | adjustment += length 202 | if length >= 0: 203 | view.insert(edit, pt, ' ' * length) 204 | else: 205 | view.erase(edit, sublime.Region(pt + length, pt)) 206 | 207 | if settings.get('mid_line_tabs') and not use_spaces: 208 | adjustment += convert_to_mid_line_tabs(view, edit, 209 | tab_size, pt, length) 210 | 211 | if len(sel) == 1: 212 | if len(view.lines(sel[0])) == 1: 213 | region = sublime.Region(0, view.size()) 214 | code = view.substr(region) 215 | for line_nums in get_blocks(code): 216 | align_lines(line_nums) 217 | else: 218 | points = [] 219 | line_nums = [view.rowcol(line.a)[0] for line in view.lines(sel[0])] 220 | align_lines(line_nums) 221 | 222 | # This handles aligning multiple selections 223 | else: 224 | max_col = max([normed_rowcol(view, region.b)[1] for region in sel]) 225 | 226 | for region in sel: 227 | length = max_col - normed_rowcol(view, region.b)[1] 228 | view.insert(edit, region.b, ' ' * length) 229 | if settings.get('mid_line_tabs') and not use_spaces: 230 | convert_to_mid_line_tabs(view, edit, tab_size, region.b, 231 | length) --------------------------------------------------------------------------------