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