Merge lp:~mkanat/loggerhead/view-default into lp:loggerhead
- view-default
- Merge into trunk-rich
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Max Kanat-Alexander | ||||
Approved revision: | 435 | ||||
Merged at revision: | 433 | ||||
Proposed branch: | lp:~mkanat/loggerhead/view-default | ||||
Merge into: | lp:loggerhead | ||||
Diff against target: |
449 lines (+149/-91) 9 files modified
loggerhead/apps/branch.py (+2/-0) loggerhead/controllers/annotate_ui.py (+54/-0) loggerhead/controllers/view_ui.py (+33/-48) loggerhead/highlight.py (+4/-2) loggerhead/static/css/view.css (+14/-12) loggerhead/templatefunctions.py (+2/-2) loggerhead/templates/inventory.pt (+8/-8) loggerhead/templates/revision.pt (+2/-2) loggerhead/templates/view.pt (+30/-17) |
||||
To merge this branch: | bzr merge lp:~mkanat/loggerhead/view-default | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Approve | ||
Michael Hudson-Doyle | Approve | ||
Review via email: mp+42222@code.launchpad.net |
Commit message
Create a new "view" controller, and have that be the default view of a file.
Description of the change
This adds a new controller, "view", and makes it the default for viewing a file, instead of "annotate". The "view" controller is just like "annotate", except that it doesn't show the revision for each line. This should significantly improve the performance of loggerhead, particularly on high-level installations (like launchpad) or on large branches (such as launchpad itself) because most of the time, people don't need the annotated information for a branch--they just want to see its content.
In actual tests, the "view" controller is between 3x and 10x faster than the "annotate" controller. (To see similar gains to these, try viewing a large file on the launchpad devel branch without any existing caches in loggerhead.)
The "view" and "annotate" controller both use the same templates, and I've renamed all of the templates and CSS to "view" instead of "annotate" (since we now think of "annotate" as a special case of "view"). I've moved annotate_ui.py to view_ui.py and created a brand-new annotate_ui.py over the old one.
Max Kanat-Alexander (mkanat) wrote : | # |
> I think it's mostly fine. The template is ugly though. Why do you need to
> put a None at the end of the annotation iterator? It would be better to pad
> out the results until you've reached the length of the text lines, or
> something.
That's true, I could do that, but that would also then involve code for keeping track of when there were extra lines in 'contents' that were beyond how far along we were with annotating. Would simplify the template, though, which I think is a valid goal. I think the problem is that 'contents' contains an extra final line when there's a "\n" at the end of the file, and annotate_iter doesn't consider that to be a line.
> If you're going to rename the annotation css file, you should probably rename
> the rules therein too.
Yeah, I suppose you're right. I was trying to keep the changes at least *somewhat* focused though, so that they could be reviewed sensibly. But now that it's passed review I'll just do that change.
> Everything else looks ok. Loading the annotation information via ajax would
> be nice, but can wait :-)
Yeah, I had the same thought.
Martin Pool (mbp) wrote : | # |
That looks plausible to me, and reasonably clean.
As we discussed elsewhere, it would be good to get this onto the Launchpad deployment branch separately from changes in loggerhead trunk that are possibly more risky, such as historydb.
I wish there was an automatic test included here, but if there is no framework in place I won't ask you to write one as part of this proposal.
- 433. By Max Kanat-Alexander
-
Instead of yielding None for when there are more lines than annotate_iter
has, just fix the off-by-one bug that made us have too many lines in
"contents" (caused by annotate_iter using split_lines and us using
split("\n") in highlight()). - 434. By Max Kanat-Alexander
-
The colors of rows in the annotated view wasn't getting set properly. There
would be a revision number, and then the *next* line would change color. - 435. By Max Kanat-Alexander
-
Rename the anno* CSS classes to view*.
Preview Diff
1 | === modified file 'loggerhead/apps/branch.py' |
2 | --- loggerhead/apps/branch.py 2010-05-21 14:07:06 +0000 |
3 | +++ loggerhead/apps/branch.py 2010-12-01 08:13:14 +0000 |
4 | @@ -29,6 +29,7 @@ |
5 | |
6 | from loggerhead.apps import static_app |
7 | from loggerhead.controllers.annotate_ui import AnnotateUI |
8 | +from loggerhead.controllers.view_ui import ViewUI |
9 | from loggerhead.controllers.atom_ui import AtomUI |
10 | from loggerhead.controllers.changelog_ui import ChangeLogUI |
11 | from loggerhead.controllers.diff_ui import DiffUI |
12 | @@ -114,6 +115,7 @@ |
13 | 'files': InventoryUI, |
14 | 'revision': RevisionUI, |
15 | 'search': SearchUI, |
16 | + 'view': ViewUI, |
17 | } |
18 | |
19 | def last_updated(self): |
20 | |
21 | === added file 'loggerhead/controllers/annotate_ui.py' |
22 | --- loggerhead/controllers/annotate_ui.py 1970-01-01 00:00:00 +0000 |
23 | +++ loggerhead/controllers/annotate_ui.py 2010-12-01 08:13:14 +0000 |
24 | @@ -0,0 +1,54 @@ |
25 | +# |
26 | +# Copyright (C) 2010 Canonical Ltd. |
27 | +# |
28 | +# This program is free software; you can redistribute it and/or modify |
29 | +# it under the terms of the GNU General Public License as published by |
30 | +# the Free Software Foundation; either version 2 of the License, or |
31 | +# (at your option) any later version. |
32 | +# |
33 | +# This program is distributed in the hope that it will be useful, |
34 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
35 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
36 | +# GNU General Public License for more details. |
37 | +# |
38 | +# You should have received a copy of the GNU General Public License |
39 | +# along with this program; if not, write to the Free Software |
40 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
41 | +# |
42 | + |
43 | +from loggerhead.controllers.view_ui import ViewUI |
44 | +from loggerhead import util |
45 | + |
46 | +class AnnotateUI(ViewUI): |
47 | + |
48 | + def annotate_file(self, info): |
49 | + file_id = info['file_id'] |
50 | + revid = info['change'].revid |
51 | + |
52 | + tree = self.tree_for(file_id, revid) |
53 | + |
54 | + change_cache = {} |
55 | + last_line_revid = None |
56 | + parity = 1 |
57 | + for line_revid, text in tree.annotate_iter(file_id): |
58 | + if line_revid == last_line_revid: |
59 | + # remember which lines have a new revno and which don't |
60 | + new_rev = False |
61 | + else: |
62 | + new_rev = True |
63 | + parity ^= 1 |
64 | + last_line_revid = line_revid |
65 | + if line_revid in change_cache: |
66 | + change = change_cache[line_revid] |
67 | + else: |
68 | + change = self._history.get_changes([line_revid])[0] |
69 | + change_cache[line_revid] = change |
70 | + |
71 | + yield util.Container( |
72 | + parity=parity, new_rev=new_rev, change=change) |
73 | + |
74 | + def get_values(self, path, kwargs, headers): |
75 | + values = super(AnnotateUI, self).get_values(path, kwargs, headers) |
76 | + values['annotated'] = self.annotate_file(values) |
77 | + |
78 | + return values |
79 | |
80 | === renamed file 'loggerhead/controllers/annotate_ui.py' => 'loggerhead/controllers/view_ui.py' |
81 | --- loggerhead/controllers/annotate_ui.py 2009-10-17 06:35:33 +0000 |
82 | +++ loggerhead/controllers/view_ui.py 2010-12-01 08:13:14 +0000 |
83 | @@ -35,20 +35,18 @@ |
84 | from loggerhead import util |
85 | |
86 | |
87 | -class AnnotateUI(TemplatedBranchView): |
88 | - |
89 | - template_path = 'loggerhead.templates.annotate' |
90 | - |
91 | - def annotate_file(self, file_id, revid): |
92 | - z = time.time() |
93 | - lineno = 1 |
94 | - parity = 0 |
95 | - |
96 | +class ViewUI(TemplatedBranchView): |
97 | + |
98 | + template_path = 'loggerhead.templates.view' |
99 | + |
100 | + def tree_for(self, file_id, revid): |
101 | file_revid = self._history.get_inventory(revid)[file_id].revision |
102 | - tree = self._history._branch.repository.revision_tree(file_revid) |
103 | + return self._history._branch.repository.revision_tree(file_revid) |
104 | |
105 | + def text_lines(self, file_id, revid): |
106 | file_name = os.path.basename(self._history.get_path(revid, file_id)) |
107 | - |
108 | + |
109 | + tree = self.tree_for(file_id, revid) |
110 | file_text = tree.get_file_text(file_id) |
111 | encoding = 'utf-8' |
112 | try: |
113 | @@ -58,44 +56,27 @@ |
114 | file_text = file_text.decode(encoding) |
115 | |
116 | file_lines = bzrlib.osutils.split_lines(file_text) |
117 | + # This can throw bzrlib.errors.BinaryFile (which our caller catches). |
118 | + bzrlib.textfile.check_text_lines(file_lines) |
119 | + |
120 | + if highlight is not None: |
121 | + hl_lines = highlight(file_name, file_text, encoding) |
122 | + # highlight strips off extra newlines at the end of the file. |
123 | + extra_lines = len(file_lines) - len(hl_lines) |
124 | + hl_lines.extend([u''] * extra_lines) |
125 | + else: |
126 | + hl_lines = map(cgi.escape, file_lines) |
127 | + |
128 | + return hl_lines; |
129 | |
130 | + def file_contents(self, file_id, revid): |
131 | try: |
132 | - bzrlib.textfile.check_text_lines(file_lines) |
133 | + file_lines = self.text_lines(file_id, revid) |
134 | except bzrlib.errors.BinaryFile: |
135 | # bail out; this isn't displayable text |
136 | - yield util.Container(parity=0, lineno=1, status='same', |
137 | - text='(This is a binary file.)', |
138 | - change=util.Container()) |
139 | - else: |
140 | - if highlight is not None: |
141 | - hl_lines = highlight(file_name, file_text, encoding) |
142 | - hl_lines.extend([u''] * (len(file_lines) - len(hl_lines))) |
143 | - else: |
144 | - hl_lines = map(cgi.escape, file_lines) |
145 | - |
146 | - change_cache = {} |
147 | - |
148 | - last_line_revid = None |
149 | - for line_revid, text in tree.annotate_iter(file_id): |
150 | - if line_revid == last_line_revid: |
151 | - # remember which lines have a new revno and which don't |
152 | - status = 'same' |
153 | - else: |
154 | - status = 'changed' |
155 | - parity ^= 1 |
156 | - last_line_revid = line_revid |
157 | - if line_revid in change_cache: |
158 | - change = change_cache[line_revid] |
159 | - else: |
160 | - change = self._history.get_changes([line_revid])[0] |
161 | - change_cache[line_revid] = change |
162 | - |
163 | - yield util.Container( |
164 | - parity=parity, lineno=lineno, status=status, |
165 | - change=change, text=hl_lines[lineno - 1]) |
166 | - lineno += 1 |
167 | - |
168 | - self.log.debug('annotate: %r secs' % (time.time() - z,)) |
169 | + return ['(This is a binary file.)'] |
170 | + |
171 | + return file_lines |
172 | |
173 | def get_values(self, path, kwargs, headers): |
174 | history = self._history |
175 | @@ -105,7 +86,7 @@ |
176 | file_id = kwargs.get('file_id', None) |
177 | if (file_id is None) and (path is None): |
178 | raise HTTPBadRequest('No file_id or filename ' |
179 | - 'provided to annotate') |
180 | + 'provided to view') |
181 | |
182 | if file_id is None: |
183 | file_id = history.get_file_id(revid, path) |
184 | @@ -140,13 +121,17 @@ |
185 | branch_breadcrumbs = util.branch_breadcrumbs(path, inv, 'files') |
186 | |
187 | return { |
188 | + # In AnnotateUI, "annotated" is a generator giving revision |
189 | + # numbers per lines, but the template checks if "annotated" is |
190 | + # true or not before using it, so we have to define it here also. |
191 | + 'annotated': False, |
192 | 'revno_url': revno_url, |
193 | 'file_id': file_id, |
194 | - 'path': path, |
195 | + 'file_path': path, |
196 | 'filename': filename, |
197 | 'navigation': navigation, |
198 | 'change': change, |
199 | - 'contents': list(self.annotate_file(file_id, revid)), |
200 | + 'contents': self.file_contents(file_id, revid), |
201 | 'fileview_active': True, |
202 | 'directory_breadcrumbs': directory_breadcrumbs, |
203 | 'branch_breadcrumbs': branch_breadcrumbs, |
204 | |
205 | === modified file 'loggerhead/highlight.py' |
206 | --- loggerhead/highlight.py 2010-04-22 03:23:55 +0000 |
207 | +++ loggerhead/highlight.py 2010-12-01 08:13:14 +0000 |
208 | @@ -16,6 +16,7 @@ |
209 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
210 | # |
211 | |
212 | +import bzrlib.osutils |
213 | import cgi |
214 | |
215 | from pygments import highlight as _highlight_func |
216 | @@ -36,7 +37,7 @@ |
217 | """ |
218 | |
219 | if len(text) > MAX_HIGHLIGHT_SIZE: |
220 | - return map(cgi.escape, text.split('\n')) |
221 | + return map(cgi.escape, bzrlib.osutils.split_lines(text)) |
222 | |
223 | formatter = HtmlFormatter(style=style, nowrap=True, classprefix='pyg-') |
224 | |
225 | @@ -48,6 +49,7 @@ |
226 | except (ClassNotFound, ValueError): |
227 | lexer = TextLexer(encoding=encoding) |
228 | |
229 | - hl_lines = _highlight_func(text, lexer, formatter).split('\n') |
230 | + hl_lines = _highlight_func(text, lexer, formatter) |
231 | + hl_lines = bzrlib.osutils.split_lines(hl_lines) |
232 | |
233 | return hl_lines |
234 | |
235 | === renamed file 'loggerhead/static/css/annotate.css' => 'loggerhead/static/css/view.css' |
236 | --- loggerhead/static/css/annotate.css 2009-04-30 10:39:05 +0000 |
237 | +++ loggerhead/static/css/view.css 2010-12-01 08:13:14 +0000 |
238 | @@ -1,29 +1,31 @@ |
239 | /*table*/ |
240 | -.annoLineTit, .annoLine, .annoRevTit, .annoRev, .annoComm, .annoCommTit, .annoContTit, .annoCont { |
241 | - width:45px; |
242 | +.viewLineTit, .viewLine, .viewRevTit, .viewRev, |
243 | +.viewContTit, .viewCont |
244 | +{ |
245 | border:1px solid #d2d2d2; |
246 | } |
247 | -.annoLine, .annoRev, .annoComm, .annoCont { |
248 | +.viewLine, .viewRev, .viewComm, .viewCont { |
249 | border:none; |
250 | } |
251 | -.annoLine { |
252 | +.viewLine, .viewLineTit { |
253 | + padding-right: .2em; |
254 | +} |
255 | +.viewLine { |
256 | width:37px; |
257 | } |
258 | -.annoContTit, .annoCont { |
259 | +.viewContTit, .viewCont { |
260 | width:auto; |
261 | + padding-left: .3em; |
262 | } |
263 | -.annoRevTit, .annoRev { |
264 | +.viewRevTit, .viewRev { |
265 | width:70px; |
266 | text-align:center; |
267 | } |
268 | -.annoComm, .annoCommTit { |
269 | - width:200px; |
270 | -} |
271 | -.annoLine, .annoCont { |
272 | +.viewLine, .viewCont { |
273 | font:normal 12px/normal monospace; |
274 | whitespace: pre; |
275 | } |
276 | -.annoCont pre { margin: 0; } |
277 | -.annoLine { |
278 | +.viewCont pre { margin: 0; } |
279 | +.viewLine { |
280 | text-align:right; |
281 | } |
282 | |
283 | === modified file 'loggerhead/templatefunctions.py' |
284 | --- loggerhead/templatefunctions.py 2009-10-17 08:47:38 +0000 |
285 | +++ loggerhead/templatefunctions.py 2010-12-01 08:13:14 +0000 |
286 | @@ -121,9 +121,9 @@ |
287 | |
288 | |
289 | @templatefunc |
290 | -def annotate_link(url, revno, path): |
291 | +def view_link(url, revno, path): |
292 | return '<a href="%s" title="Annotate %s">%s</a>' % ( |
293 | - url(['/annotate', revno, path]), cgi.escape(path), cgi.escape(path)) |
294 | + url(['/view', revno, path]), cgi.escape(path), cgi.escape(path)) |
295 | |
296 | @templatefunc |
297 | def revision_link(url, revno, path, frag=''): |
298 | |
299 | === modified file 'loggerhead/templates/inventory.pt' |
300 | --- loggerhead/templates/inventory.pt 2009-10-17 06:55:25 +0000 |
301 | +++ loggerhead/templates/inventory.pt 2010-12-01 08:13:14 +0000 |
302 | @@ -89,11 +89,11 @@ |
303 | |
304 | <!-- Show this if it's a symlink --> |
305 | <tr tal:attributes="class string:blueRow${repeat/file/even}" tal:condition="python:file.kind=='symlink'"> |
306 | - <td class="autcell"><a tal:attributes="href python:url(['/annotate', change.revno, file.absolutepath])"> |
307 | + <td class="autcell"><a tal:attributes="href python:url(['/view', change.revno, file.absolutepath])"> |
308 | <img tal:attributes="src python:branch.static_url('/static/images/ico_file_flecha.gif')" alt="Symlink" /> |
309 | </a> |
310 | |
311 | - <a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath])" |
312 | + <a tal:attributes="href python:url(['/view', revno_url, file.absolutepath])" |
313 | tal:content="file/filename" class="link"></a> |
314 | </td> |
315 | <td class="date"><a tal:attributes="href python:url(['/revision', file.change.revno]); |
316 | @@ -113,17 +113,17 @@ |
317 | |
318 | <!-- Show this if it's a regular file --> |
319 | <tr tal:attributes="class string:blueRow${repeat/file/even}" tal:condition="python:file.kind=='file'"> |
320 | - <td class="autcell"><a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath])"> |
321 | + <td class="autcell"><a tal:attributes="href python:url(['/view', revno_url, file.absolutepath])"> |
322 | <img tal:attributes="src python:branch.static_url('/static/images/ico_file.gif'); |
323 | - title string:Annotate ${file/filename}" |
324 | + title string:View ${file/filename}" |
325 | tal:condition="python:file.executable is False" /> |
326 | <!-- Show a different icon id the file executable --> |
327 | <img tal:attributes="src python:branch.static_url('/static/images/ico_file_modify.gif'); |
328 | - title string:Annotate ${file/filename}" |
329 | + title string:View ${file/filename}" |
330 | tal:condition="python:file.executable is True" alt="File" /> |
331 | </a> |
332 | |
333 | - <a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath])" |
334 | + <a tal:attributes="href python:url(['/view', revno_url, file.absolutepath])" |
335 | tal:content="file/filename" class="link"></a></td> |
336 | <td class="date"><a tal:attributes="href python:url(['/revision', file.change.revno]); |
337 | title string:Show revision ${file/change/revno}" |
338 | @@ -131,8 +131,8 @@ |
339 | </td> |
340 | <td class="date" tal:content="python:util.date_time(file.change.date)"></td> |
341 | <td class="timedate2" tal:content="python:util.human_size(file.size)"></td> |
342 | - <td class="expcell"><a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath]); |
343 | - title string:Annotate ${file/filename}"> |
344 | + <td class="expcell"><a tal:attributes="href python:url(['/view', revno_url, file.absolutepath]); |
345 | + title string:View ${file/filename}"> |
346 | <img tal:attributes="src python:branch.static_url('/static/images/ico_planilla.gif')" alt="Diff" /> |
347 | </a> |
348 | </td> |
349 | |
350 | === modified file 'loggerhead/templates/revision.pt' |
351 | --- loggerhead/templates/revision.pt 2009-04-28 03:01:54 +0000 |
352 | +++ loggerhead/templates/revision.pt 2010-12-01 08:13:14 +0000 |
353 | @@ -42,7 +42,7 @@ |
354 | </tal:compare-to> |
355 | </span> |
356 | <span class="breadcrumb" tal:condition="specific_path"> |
357 | - : <tal:annotate content="structure python:annotate_link(url, change.revno, specific_path)" /> |
358 | + : <tal:annotate content="structure python:view_link(url, change.revno, specific_path)" /> |
359 | </span> |
360 | </h1> |
361 | <tal:branch-info replace="structure python:branchinfo(branch)" /> |
362 | @@ -55,7 +55,7 @@ |
363 | </a> |
364 | </p> |
365 | <p tal:condition="specific_path"> |
366 | - Viewing changes to <tal:annotate content="structure python:annotate_link(url, change.revno, specific_path)" /> |
367 | + Viewing changes to <tal:annotate content="structure python:view_link(url, change.revno, specific_path)" /> |
368 | </p> |
369 | <ul id="submenuTabs"> |
370 | <li id="first"><a tal:attributes="href python:url(['/files', change.revno]); |
371 | |
372 | === renamed file 'loggerhead/templates/annotate.pt' => 'loggerhead/templates/view.pt' |
373 | --- loggerhead/templates/annotate.pt 2009-04-30 10:39:05 +0000 |
374 | +++ loggerhead/templates/view.pt 2010-12-01 08:13:14 +0000 |
375 | @@ -3,12 +3,12 @@ |
376 | <html xmlns="http://www.w3.org/1999/xhtml" metal:use-macro="macros/main"> |
377 | <head> |
378 | <title metal:fill-slot="title" |
379 | - tal:content="string:${branch/friendly_name} : contents of ${path} |
380 | + tal:content="string:${branch/friendly_name} : contents of ${file_path} |
381 | at revision ${change/revno}"> |
382 | </title> |
383 | <metal:block fill-slot="header_extras"> |
384 | <link rel="stylesheet" type="text/css" media="all" |
385 | - tal:attributes="href python:branch.static_url('/static/css/annotate.css')"/> |
386 | + tal:attributes="href python:branch.static_url('/static/css/view.css')"/> |
387 | <link rel="stylesheet" type="text/css" media="all" |
388 | tal:attributes="href python:branch.static_url('/static/css/highlight.css')"/> |
389 | </metal:block> |
390 | @@ -36,6 +36,14 @@ |
391 | <li id="first"> |
392 | <a tal:attributes="href python:url(['/files', change.revno], clear=1)">browse files</a> |
393 | </li> |
394 | + <li tal:condition="not:annotated"> |
395 | + <a tal:attributes="href python:url(['/annotate', revno_url, file_path], clear=1)" |
396 | + >view revision numbers per line</a> |
397 | + </li> |
398 | + <li tal:condition="annotated"> |
399 | + <a tal:attributes="href python:url(['/view', revno_url, file_path], clear=1)" |
400 | + >view without revision numbers</a> |
401 | + </li> |
402 | <li> |
403 | <a tal:attributes="href python:url(['/revision', change.revno], clear=1)">view revision</a> |
404 | </li> |
405 | @@ -50,25 +58,30 @@ |
406 | </li> |
407 | </ul> |
408 | |
409 | - <div class="annotate"> |
410 | + <div class="view"> |
411 | <table id="logentries"> |
412 | <tr class="logheader"> |
413 | - <td class="annoLineTit">Line</td> |
414 | - <td class="annoRevTit">Revision</td> |
415 | - <td class="annoContTit">Contents</td> |
416 | + <td class="viewLineTit">Line</td> |
417 | + <td class="viewRevTit" tal:condition="annotated">Revision</td> |
418 | + <td class="viewContTit">Contents</td> |
419 | </tr> |
420 | |
421 | - <tr tal:repeat="line contents" |
422 | - tal:attributes="class string:blueRow${line/parity}"> |
423 | - <td class="annoLine"><a tal:attributes="id string:L${line/lineno}; href string:#L${line/lineno}" tal:content="line/lineno">1</a></td> |
424 | - <td class="annoRev"> |
425 | - <a tal:condition="python:line.status=='changed'" |
426 | - tal:content="python:util.trunc(line.change.revno)" |
427 | - tal:attributes="href python:url(['/revision', line.change.revno], clear=1); |
428 | - title python:'%s by %s, on %s (%s)'%(line.change.revno, ', '.join(util.hide_emails(line.change.authors)), line.change.date.strftime('%d %b %Y %H:%M'), util.date_time(line.change.date))"></a> |
429 | - </td> |
430 | - <td class="annoCont"><pre tal:content="structure line/text"></pre></td> |
431 | - </tr> |
432 | + <tal:rep tal:repeat="line contents"> |
433 | + <tr tal:define="anno python:annotated and annotated.next()" |
434 | + tal:attributes="class python:anno and 'blueRow' + str(anno.parity) or None"> |
435 | + <td class="viewLine"> |
436 | + <a tal:attributes="id string:L${repeat/line/number}; href string:#L${repeat/line/number}" |
437 | + tal:content="repeat/line/number">1</a> |
438 | + </td> |
439 | + <td class="viewRev" tal:condition="annotated"> |
440 | + <a tal:condition="python:anno.new_rev" |
441 | + tal:content="python:util.trunc(anno.change.revno)" |
442 | + tal:attributes="href python:url(['/revision', anno.change.revno], clear=1); |
443 | + title python:'%s by %s, on %s (%s)' % (anno.change.revno, ', '.join(util.hide_emails(anno.change.authors)), anno.change.date.strftime('%d %b %Y %H:%M'), util.date_time(anno.change.date))"></a> |
444 | + </td> |
445 | + <td class="viewCont"><pre tal:content="structure line"></pre></td> |
446 | + </tr> |
447 | + </tal:rep> |
448 | </table> |
449 | </div> |
450 |
Yay for progress on this!
I think it's mostly fine. The template is ugly though. Why do you need to put a None at the end of the annotation iterator? It would be better to pad out the results until you've reached the length of the text lines, or something.
If you're going to rename the annotation css file, you should probably rename the rules therein too.
Everything else looks ok. Loading the annotation information via ajax would be nice, but can wait :-)
Cheers,
mwh