1import re 2 3from rope.base import codeanalyze 4 5 6class TextIndenter(object): 7 """A class for formatting texts""" 8 9 def __init__(self, editor, indents=4): 10 self.editor = editor 11 self.indents = indents 12 self.line_editor = editor.line_editor() 13 14 def correct_indentation(self, lineno): 15 """Correct the indentation of a line""" 16 17 def deindent(self, lineno): 18 """Deindent the a line""" 19 current_indents = self._count_line_indents(lineno) 20 new_indents = max(0, current_indents - self.indents) 21 self._set_line_indents(lineno, new_indents) 22 23 def indent(self, lineno): 24 """Indent a line""" 25 current_indents = self._count_line_indents(lineno) 26 new_indents = current_indents + self.indents 27 self._set_line_indents(lineno, new_indents) 28 29 def entering_new_line(self, lineno): 30 """Indent a line 31 32 Uses `correct_indentation` and last line indents 33 """ 34 last_line = "" 35 if lineno > 1: 36 last_line = self.line_editor.get_line(lineno - 1) 37 if last_line.strip() == '': 38 self._set_line_indents(lineno, len(last_line)) 39 else: 40 self.correct_indentation(lineno) 41 42 def insert_tab(self, index): 43 """Inserts a tab in the given index""" 44 self.editor.insert(index, ' ' * self.indents) 45 46 def _set_line_indents(self, lineno, indents): 47 old_indents = self._count_line_indents(lineno) 48 indent_diffs = indents - old_indents 49 self.line_editor.indent_line(lineno, indent_diffs) 50 51 def _count_line_indents(self, lineno): 52 contents = self.line_editor.get_line(lineno) 53 result = 0 54 for x in contents: 55 if x == ' ': 56 result += 1 57 elif x == '\t': 58 result += 8 59 else: 60 break 61 return result 62 63 64class NormalIndenter(TextIndenter): 65 66 def __init__(self, editor): 67 super(NormalIndenter, self).__init__(editor) 68 69 def correct_indentation(self, lineno): 70 prev_indents = 0 71 if lineno > 1: 72 prev_indents = self._count_line_indents(lineno - 1) 73 self._set_line_indents(lineno, prev_indents) 74 75 76class PythonCodeIndenter(TextIndenter): 77 78 def __init__(self, editor, indents=4): 79 super(PythonCodeIndenter, self).__init__(editor, indents) 80 81 def _last_non_blank(self, lineno): 82 current_line = lineno - 1 83 while current_line != 1 and \ 84 self.line_editor.get_line(current_line).strip() == '': 85 current_line -= 1 86 return current_line 87 88 def _get_correct_indentation(self, lineno): 89 if lineno == 1: 90 return 0 91 new_indent = self._get_base_indentation(lineno) 92 93 prev_lineno = self._last_non_blank(lineno) 94 prev_line = self.line_editor.get_line(prev_lineno) 95 if prev_lineno == lineno or prev_line.strip() == '': 96 new_indent = 0 97 current_line = self.line_editor.get_line(lineno) 98 new_indent += self._indents_caused_by_current_stmt(current_line) 99 return new_indent 100 101 def _get_base_indentation(self, lineno): 102 range_finder = _StatementRangeFinder( 103 self.line_editor, self._last_non_blank(lineno)) 104 start = range_finder.get_statement_start() 105 if not range_finder.is_line_continued(): 106 changes = self._indents_caused_by_prev_stmt( 107 (start, self._last_non_blank(lineno))) 108 return self._count_line_indents(start) + changes 109 if range_finder.last_open_parens(): 110 open_parens = range_finder.last_open_parens() 111 parens_line = self.line_editor.get_line(open_parens[0]) 112 if parens_line[open_parens[1] + 1:].strip() == '': 113 if len(range_finder.open_parens) > 1: 114 return range_finder.open_parens[-2][1] + 1 115 else: 116 return self._count_line_indents(start) + self.indents 117 return range_finder.last_open_parens()[1] + 1 118 119 start_line = self.line_editor.get_line(start) 120 if start == lineno - 1: 121 try: 122 equals_index = start_line.index(' = ') + 1 123 if start_line[equals_index + 1:].strip() == '\\': 124 return self._count_line_indents(start) + self.indents 125 return equals_index + 2 126 except ValueError: 127 match = re.search(r'(\b )|(\.)', start_line) 128 if match: 129 return match.start() + 1 130 else: 131 return len(start_line) + 1 132 else: 133 return self._count_line_indents(self._last_non_blank(lineno)) 134 135 def _indents_caused_by_prev_stmt(self, stmt_range): 136 first_line = self.line_editor.get_line(stmt_range[0]) 137 last_line = self.line_editor.get_line(stmt_range[1]) 138 new_indent = 0 139 if self._strip(last_line).endswith(':'): 140 new_indent += self.indents 141 if self._startswith(first_line, ('return', 'raise', 'pass', 142 'break', 'continue')): 143 new_indent -= self.indents 144 return new_indent 145 146 def _startswith(self, line, tokens): 147 line = self._strip(line) 148 for token in tokens: 149 if line == token or line.startswith(token + ' '): 150 return True 151 152 def _strip(self, line): 153 try: 154 numsign = line.rindex('#') 155 comment = line[numsign:] 156 if '\'' not in comment and '\"' not in comment: 157 line = line[:numsign] 158 except ValueError: 159 pass 160 return line.strip() 161 162 def _indents_caused_by_current_stmt(self, current_line): 163 new_indent = 0 164 if self._strip(current_line) == 'else:': 165 new_indent -= self.indents 166 if self._strip(current_line) == 'finally:': 167 new_indent -= self.indents 168 if self._startswith(current_line, ('elif',)): 169 new_indent -= self.indents 170 if self._startswith(current_line, ('except',)) and \ 171 self._strip(current_line).endswith(':'): 172 new_indent -= self.indents 173 return new_indent 174 175 def correct_indentation(self, lineno): 176 """Correct the indentation of the line containing the given index""" 177 self._set_line_indents(lineno, self._get_correct_indentation(lineno)) 178 179 180class _StatementRangeFinder(object): 181 """A method object for finding the range of a statement""" 182 183 def __init__(self, lines, lineno): 184 self.lines = lines 185 self.lineno = lineno 186 self.in_string = '' 187 self.open_count = 0 188 self.explicit_continuation = False 189 self.open_parens = [] 190 self._analyze() 191 192 def _analyze_line(self, lineno): 193 current_line = self.lines.get_line(lineno) 194 for i, char in enumerate(current_line): 195 if char in '\'"': 196 if self.in_string == '': 197 self.in_string = char 198 if char * 3 == current_line[i:i + 3]: 199 self.in_string = char * 3 200 elif self.in_string == current_line[i:i + len(self.in_string)] and \ 201 not (i > 0 and current_line[i - 1] == '\\' and 202 not (i > 1 and current_line[i - 2:i] == '\\\\')): 203 self.in_string = '' 204 if self.in_string != '': 205 continue 206 if char == '#': 207 break 208 if char in '([{': 209 self.open_count += 1 210 self.open_parens.append((lineno, i)) 211 if char in ')]}': 212 self.open_count -= 1 213 if self.open_parens: 214 self.open_parens.pop() 215 if current_line and char != '#' and current_line.endswith('\\'): 216 self.explicit_continuation = True 217 else: 218 self.explicit_continuation = False 219 220 def _analyze(self): 221 last_statement = 1 222 block_start = codeanalyze.get_block_start(self.lines, self.lineno) 223 for current_line_number in range(block_start, self.lineno + 1): 224 if not self.explicit_continuation and \ 225 self.open_count == 0 and self.in_string == '': 226 last_statement = current_line_number 227 self._analyze_line(current_line_number) 228 self.statement_start = last_statement 229 230 def get_statement_start(self): 231 return self.statement_start 232 233 def last_open_parens(self): 234 if not self.open_parens: 235 return None 236 return self.open_parens[-1] 237 238 def is_line_continued(self): 239 return self.open_count != 0 or self.explicit_continuation 240 241 def get_line_indents(self, line_number): 242 return self._count_line_indents(self.lines.get_line(line_number)) 243