Mercurial > public > mercurial-scm > hg
comparison mercurial/graphmod.py @ 17179:0849d725e2f9
graphlog: extract ascii drawing code into graphmod
author | Patrick Mezard <patrick@mezard.eu> |
---|---|
date | Wed, 11 Jul 2012 17:13:39 +0200 |
parents | 6e4de55a41a4 |
children | e441657b372b |
comparison
equal
deleted
inserted
replaced
17178:8308f6284640 | 17179:0849d725e2f9 |
---|---|
161 kept.add(r) | 161 kept.add(r) |
162 else: | 162 else: |
163 pending.update([p for p in cl.parentrevs(r)]) | 163 pending.update([p for p in cl.parentrevs(r)]) |
164 seen.add(r) | 164 seen.add(r) |
165 return sorted(kept) | 165 return sorted(kept) |
166 | |
167 def asciiedges(type, char, lines, seen, rev, parents): | |
168 """adds edge info to changelog DAG walk suitable for ascii()""" | |
169 if rev not in seen: | |
170 seen.append(rev) | |
171 nodeidx = seen.index(rev) | |
172 | |
173 knownparents = [] | |
174 newparents = [] | |
175 for parent in parents: | |
176 if parent in seen: | |
177 knownparents.append(parent) | |
178 else: | |
179 newparents.append(parent) | |
180 | |
181 ncols = len(seen) | |
182 nextseen = seen[:] | |
183 nextseen[nodeidx:nodeidx + 1] = newparents | |
184 edges = [(nodeidx, nextseen.index(p)) for p in knownparents] | |
185 | |
186 while len(newparents) > 2: | |
187 # ascii() only knows how to add or remove a single column between two | |
188 # calls. Nodes with more than two parents break this constraint so we | |
189 # introduce intermediate expansion lines to grow the active node list | |
190 # slowly. | |
191 edges.append((nodeidx, nodeidx)) | |
192 edges.append((nodeidx, nodeidx + 1)) | |
193 nmorecols = 1 | |
194 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) | |
195 char = '\\' | |
196 lines = [] | |
197 nodeidx += 1 | |
198 ncols += 1 | |
199 edges = [] | |
200 del newparents[0] | |
201 | |
202 if len(newparents) > 0: | |
203 edges.append((nodeidx, nodeidx)) | |
204 if len(newparents) > 1: | |
205 edges.append((nodeidx, nodeidx + 1)) | |
206 nmorecols = len(nextseen) - ncols | |
207 seen[:] = nextseen | |
208 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) | |
209 | |
210 def _fixlongrightedges(edges): | |
211 for (i, (start, end)) in enumerate(edges): | |
212 if end > start: | |
213 edges[i] = (start, end + 1) | |
214 | |
215 def _getnodelineedgestail( | |
216 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): | |
217 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: | |
218 # Still going in the same non-vertical direction. | |
219 if n_columns_diff == -1: | |
220 start = max(node_index + 1, p_node_index) | |
221 tail = ["|", " "] * (start - node_index - 1) | |
222 tail.extend(["/", " "] * (n_columns - start)) | |
223 return tail | |
224 else: | |
225 return ["\\", " "] * (n_columns - node_index - 1) | |
226 else: | |
227 return ["|", " "] * (n_columns - node_index - 1) | |
228 | |
229 def _drawedges(edges, nodeline, interline): | |
230 for (start, end) in edges: | |
231 if start == end + 1: | |
232 interline[2 * end + 1] = "/" | |
233 elif start == end - 1: | |
234 interline[2 * start + 1] = "\\" | |
235 elif start == end: | |
236 interline[2 * start] = "|" | |
237 else: | |
238 if 2 * end >= len(nodeline): | |
239 continue | |
240 nodeline[2 * end] = "+" | |
241 if start > end: | |
242 (start, end) = (end, start) | |
243 for i in range(2 * start + 1, 2 * end): | |
244 if nodeline[i] != "+": | |
245 nodeline[i] = "-" | |
246 | |
247 def _getpaddingline(ni, n_columns, edges): | |
248 line = [] | |
249 line.extend(["|", " "] * ni) | |
250 if (ni, ni - 1) in edges or (ni, ni) in edges: | |
251 # (ni, ni - 1) (ni, ni) | |
252 # | | | | | | | | | |
253 # +---o | | o---+ | |
254 # | | c | | c | | | |
255 # | |/ / | |/ / | |
256 # | | | | | | | |
257 c = "|" | |
258 else: | |
259 c = " " | |
260 line.extend([c, " "]) | |
261 line.extend(["|", " "] * (n_columns - ni - 1)) | |
262 return line | |
263 | |
264 def asciistate(): | |
265 """returns the initial value for the "state" argument to ascii()""" | |
266 return [0, 0] | |
267 | |
268 def ascii(ui, state, type, char, text, coldata): | |
269 """prints an ASCII graph of the DAG | |
270 | |
271 takes the following arguments (one call per node in the graph): | |
272 | |
273 - ui to write to | |
274 - Somewhere to keep the needed state in (init to asciistate()) | |
275 - Column of the current node in the set of ongoing edges. | |
276 - Type indicator of node data, usually 'C' for changesets. | |
277 - Payload: (char, lines): | |
278 - Character to use as node's symbol. | |
279 - List of lines to display as the node's text. | |
280 - Edges; a list of (col, next_col) indicating the edges between | |
281 the current node and its parents. | |
282 - Number of columns (ongoing edges) in the current revision. | |
283 - The difference between the number of columns (ongoing edges) | |
284 in the next revision and the number of columns (ongoing edges) | |
285 in the current revision. That is: -1 means one column removed; | |
286 0 means no columns added or removed; 1 means one column added. | |
287 """ | |
288 | |
289 idx, edges, ncols, coldiff = coldata | |
290 assert -2 < coldiff < 2 | |
291 if coldiff == -1: | |
292 # Transform | |
293 # | |
294 # | | | | | | | |
295 # o | | into o---+ | |
296 # |X / |/ / | |
297 # | | | | | |
298 _fixlongrightedges(edges) | |
299 | |
300 # add_padding_line says whether to rewrite | |
301 # | |
302 # | | | | | | | | | |
303 # | o---+ into | o---+ | |
304 # | / / | | | # <--- padding line | |
305 # o | | | / / | |
306 # o | | | |
307 add_padding_line = (len(text) > 2 and coldiff == -1 and | |
308 [x for (x, y) in edges if x + 1 < y]) | |
309 | |
310 # fix_nodeline_tail says whether to rewrite | |
311 # | |
312 # | | o | | | | o | | | |
313 # | | |/ / | | |/ / | |
314 # | o | | into | o / / # <--- fixed nodeline tail | |
315 # | |/ / | |/ / | |
316 # o | | o | | | |
317 fix_nodeline_tail = len(text) <= 2 and not add_padding_line | |
318 | |
319 # nodeline is the line containing the node character (typically o) | |
320 nodeline = ["|", " "] * idx | |
321 nodeline.extend([char, " "]) | |
322 | |
323 nodeline.extend( | |
324 _getnodelineedgestail(idx, state[1], ncols, coldiff, | |
325 state[0], fix_nodeline_tail)) | |
326 | |
327 # shift_interline is the line containing the non-vertical | |
328 # edges between this entry and the next | |
329 shift_interline = ["|", " "] * idx | |
330 if coldiff == -1: | |
331 n_spaces = 1 | |
332 edge_ch = "/" | |
333 elif coldiff == 0: | |
334 n_spaces = 2 | |
335 edge_ch = "|" | |
336 else: | |
337 n_spaces = 3 | |
338 edge_ch = "\\" | |
339 shift_interline.extend(n_spaces * [" "]) | |
340 shift_interline.extend([edge_ch, " "] * (ncols - idx - 1)) | |
341 | |
342 # draw edges from the current node to its parents | |
343 _drawedges(edges, nodeline, shift_interline) | |
344 | |
345 # lines is the list of all graph lines to print | |
346 lines = [nodeline] | |
347 if add_padding_line: | |
348 lines.append(_getpaddingline(idx, ncols, edges)) | |
349 lines.append(shift_interline) | |
350 | |
351 # make sure that there are as many graph lines as there are | |
352 # log strings | |
353 while len(text) < len(lines): | |
354 text.append("") | |
355 if len(lines) < len(text): | |
356 extra_interline = ["|", " "] * (ncols + coldiff) | |
357 while len(lines) < len(text): | |
358 lines.append(extra_interline) | |
359 | |
360 # print lines | |
361 indentation_level = max(ncols, ncols + coldiff) | |
362 for (line, logstr) in zip(lines, text): | |
363 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) | |
364 ui.write(ln.rstrip() + '\n') | |
365 | |
366 # ... and start over | |
367 state[0] = coldiff | |
368 state[1] = idx |