Attachment 'reindent.py'
Download 1 import gedit
2 __version__ = "1"
3
4 import tokenize
5 import os
6 import sys
7 import gtk
8
9 verbose = 0
10 recurse = 0
11 dryrun = 0
12
13
14 def _rstrip(line, JUNK='\n \t'):
15 """Return line stripped of trailing spaces, tabs, newlines.
16
17 Note that line.rstrip() instead also strips sundry control characters,
18 but at least one known Emacs user expects to keep junk like that, not
19 mentioning Barry by name or anything <wink>.
20 """
21
22 i = len(line)
23 while i > 0 and line[i-1] in JUNK:
24 i -= 1
25 return line[:i]
26
27 class Reindenter:
28
29 def __init__(self, text):
30 self.find_stmt = 1 # next token begins a fresh stmt?
31 self.level = 0 # current indent level
32
33 # Raw file lines.
34 self.raw = text
35
36 # File lines, rstripped & tab-expanded. Dummy at start is so
37 # that we can use tokenize's 1-based line numbering easily.
38 # Note that a line is all-blank iff it's "\n".
39 self.lines = [_rstrip(line).expandtabs() + "\n"
40 for line in self.raw]
41 self.lines.insert(0, None)
42 self.index = 1 # index into self.lines of next line
43
44 # List of (lineno, indentlevel) pairs, one for each stmt and
45 # comment line. indentlevel is -1 for comment lines, as a
46 # signal that tokenize doesn't know what to do about them;
47 # indeed, they're our headache!
48 self.stats = []
49
50 def run(self):
51 tokenize.tokenize(self.getline, self.tokeneater)
52 # Remove trailing empty lines.
53 lines = self.lines
54 while lines and lines[-1] == "\n":
55 lines.pop()
56 # Sentinel.
57 stats = self.stats
58 stats.append((len(lines), 0))
59 # Map count of leading spaces to # we want.
60 have2want = {}
61 # Program after transformation.
62 after = self.after = []
63 # Copy over initial empty lines -- there's nothing to do until
64 # we see a line with *something* on it.
65 i = stats[0][0]
66 after.extend(lines[1:i])
67 for i in range(len(stats)-1):
68 thisstmt, thislevel = stats[i]
69 nextstmt = stats[i+1][0]
70 have = getlspace(lines[thisstmt])
71 want = thislevel * 4
72 if want < 0:
73 # A comment line.
74 if have:
75 # An indented comment line. If we saw the same
76 # indentation before, reuse what it most recently
77 # mapped to.
78 want = have2want.get(have, -1)
79 if want < 0:
80 # Then it probably belongs to the next real stmt.
81 for j in xrange(i+1, len(stats)-1):
82 jline, jlevel = stats[j]
83 if jlevel >= 0:
84 if have == getlspace(lines[jline]):
85 want = jlevel * 4
86 break
87 if want < 0: # Maybe it's a hanging
88 # comment like this one,
89 # in which case we should shift it like its base
90 # line got shifted.
91 for j in xrange(i-1, -1, -1):
92 jline, jlevel = stats[j]
93 if jlevel >= 0:
94 want = have + getlspace(after[jline-1]) - \
95 getlspace(lines[jline])
96 break
97 if want < 0:
98 # Still no luck -- leave it alone.
99 want = have
100 else:
101 want = 0
102 assert want >= 0
103 have2want[have] = want
104 diff = want - have
105 if diff == 0 or have == 0:
106 after.extend(lines[thisstmt:nextstmt])
107 else:
108 for line in lines[thisstmt:nextstmt]:
109 if diff > 0:
110 if line == "\n":
111 after.append(line)
112 else:
113 after.append(" " * diff + line)
114 else:
115 remove = min(getlspace(line), -diff)
116 after.append(line[remove:])
117 return self.raw != self.after
118
119
120 # Line-getter for tokenize.
121 def getline(self):
122 if self.index >= len(self.lines):
123 line = ""
124 else:
125 line = self.lines[self.index]
126 self.index += 1
127 return line
128
129 # Line-eater for tokenize.
130 def tokeneater(self, type, token, (sline, scol), end, line,
131 INDENT=tokenize.INDENT,
132 DEDENT=tokenize.DEDENT,
133 NEWLINE=tokenize.NEWLINE,
134 COMMENT=tokenize.COMMENT,
135 NL=tokenize.NL):
136
137 if type == NEWLINE:
138 # A program statement, or ENDMARKER, will eventually follow,
139 # after some (possibly empty) run of tokens of the form
140 # (NL | COMMENT)* (INDENT | DEDENT+)?
141 self.find_stmt = 1
142
143 elif type == INDENT:
144 self.find_stmt = 1
145 self.level += 1
146
147 elif type == DEDENT:
148 self.find_stmt = 1
149 self.level -= 1
150
151 elif type == COMMENT:
152 if self.find_stmt:
153 self.stats.append((sline, -1))
154 # but we're still looking for a new stmt, so leave
155 # find_stmt alone
156
157 elif type == NL:
158 pass
159
160 elif self.find_stmt:
161 # This is the first "real token" following a NEWLINE, so it
162 # must be the first token of the next program statement, or an
163 # ENDMARKER.
164 self.find_stmt = 0
165 if line: # not endmarker
166 self.stats.append((sline, self.level))
167
168 # Count number of leading blanks.
169 def getlspace(line):
170 i, n = 0, len(line)
171 while i < n and line[i] == " ":
172 i += 1
173 return i
174
175 class ReindentPython(gedit.Plugin):
176 def __init__(self):
177 gedit.Plugin.__init__(self)
178
179 def activate(self, window):
180 actions = [
181 ("Reindent", None, "Reindent code", None,"Reindent code following PEP008 Guideline", self.reindent)]
182 windowdata = dict()
183 window.set_data("ReindentPluginWindowDataKey", windowdata)
184 windowdata["reindent_action_group"] = gtk.ActionGroup("GeditReindentPluginActions")
185 windowdata["reindent_action_group"].add_actions(actions, window)
186 manager = window.get_ui_manager()
187 manager.insert_action_group(windowdata["reindent_action_group"], -1)
188 windowdata["ui_id"] = manager.new_merge_id ()
189
190 action = gtk.ActionGroup("GeditReindentPluginActions")
191 manager = window.get_ui_manager()
192 manager.insert_action_group(action, -1)
193 submenu = """
194 <ui>
195 <menubar name='MenuBar'>
196 <menu name='ToolsMenu' action='Tools'>
197 <placeholder name='ToolsOps_2'>
198 <menuitem action='Reindent'/>
199 <separator/>
200 </placeholder>
201 </menu>
202 </menubar>
203 </ui>"""
204 manager.add_ui_from_string(submenu)
205
206
207 def deactivate(self, window):
208 windowdata = window.get_data("ReindentPluginWindowDataKey")
209 manager = window.get_ui_manager()
210 manager.remove_ui(windowdata["ui_id"])
211 manager.remove_action_group(windowdata["reindent_action_group"])
212
213 def update_ui(self, window):
214 view = window.get_active_view()
215 windowdata = window.get_data("ReindentPluginWindowDataKey")
216 windowdata["reindent_action_group"].set_sensitive(bool(view and view.get_editable()))
217
218 def reindent(self, widget, window):
219 doc = window.get_active_document()
220 bounds = doc.get_bounds()
221 text = doc.get_text(*bounds)
222 text_array = text.split("\n")
223 r = Reindenter(text_array)
224 r.run()
225 text = ""
226 for i in r.after:
227 text += i
228 doc.set_text(text)
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.