##// END OF EJS Templates
bookmarks: move color style to color
Matt Mackall -
r13361:5b425236 default
parent child Browse files
Show More
@@ -1,360 +1,358 b''
1 # Mercurial extension to provide the 'hg bookmark' command
1 # Mercurial extension to provide the 'hg bookmark' command
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''track a line of development with movable markers
8 '''track a line of development with movable markers
9
9
10 Bookmarks are local movable markers to changesets. Every bookmark
10 Bookmarks are local movable markers to changesets. Every bookmark
11 points to a changeset identified by its hash. If you commit a
11 points to a changeset identified by its hash. If you commit a
12 changeset that is based on a changeset that has a bookmark on it, the
12 changeset that is based on a changeset that has a bookmark on it, the
13 bookmark shifts to the new changeset.
13 bookmark shifts to the new changeset.
14
14
15 It is possible to use bookmark names in every revision lookup (e.g.
15 It is possible to use bookmark names in every revision lookup (e.g.
16 :hg:`merge`, :hg:`update`).
16 :hg:`merge`, :hg:`update`).
17
17
18 By default, when several bookmarks point to the same changeset, they
18 By default, when several bookmarks point to the same changeset, they
19 will all move forward together. It is possible to obtain a more
19 will all move forward together. It is possible to obtain a more
20 git-like experience by adding the following configuration option to
20 git-like experience by adding the following configuration option to
21 your configuration file::
21 your configuration file::
22
22
23 [bookmarks]
23 [bookmarks]
24 track.current = True
24 track.current = True
25
25
26 This will cause Mercurial to track the bookmark that you are currently
26 This will cause Mercurial to track the bookmark that you are currently
27 using, and only update it. This is similar to git's approach to
27 using, and only update it. This is similar to git's approach to
28 branching.
28 branching.
29 '''
29 '''
30
30
31 from mercurial.i18n import _
31 from mercurial.i18n import _
32 from mercurial.node import nullid, nullrev, bin, hex, short
32 from mercurial.node import nullid, nullrev, bin, hex, short
33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
34 from mercurial import encoding
34 from mercurial import encoding
35 from mercurial import bookmarks
35 from mercurial import bookmarks
36 import os
36 import os
37
37
38 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
38 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
39 '''track a line of development with movable markers
39 '''track a line of development with movable markers
40
40
41 Bookmarks are pointers to certain commits that move when
41 Bookmarks are pointers to certain commits that move when
42 committing. Bookmarks are local. They can be renamed, copied and
42 committing. Bookmarks are local. They can be renamed, copied and
43 deleted. It is possible to use bookmark names in :hg:`merge` and
43 deleted. It is possible to use bookmark names in :hg:`merge` and
44 :hg:`update` to merge and update respectively to a given bookmark.
44 :hg:`update` to merge and update respectively to a given bookmark.
45
45
46 You can use :hg:`bookmark NAME` to set a bookmark on the working
46 You can use :hg:`bookmark NAME` to set a bookmark on the working
47 directory's parent revision with the given name. If you specify
47 directory's parent revision with the given name. If you specify
48 a revision using -r REV (where REV may be an existing bookmark),
48 a revision using -r REV (where REV may be an existing bookmark),
49 the bookmark is assigned to that revision.
49 the bookmark is assigned to that revision.
50
50
51 Bookmarks can be pushed and pulled between repositories (see :hg:`help
51 Bookmarks can be pushed and pulled between repositories (see :hg:`help
52 push` and :hg:`help pull`). This requires the bookmark extension to be
52 push` and :hg:`help pull`). This requires the bookmark extension to be
53 enabled for both the local and remote repositories.
53 enabled for both the local and remote repositories.
54 '''
54 '''
55 hexfn = ui.debugflag and hex or short
55 hexfn = ui.debugflag and hex or short
56 marks = repo._bookmarks
56 marks = repo._bookmarks
57 cur = repo.changectx('.').node()
57 cur = repo.changectx('.').node()
58
58
59 if rename:
59 if rename:
60 if rename not in marks:
60 if rename not in marks:
61 raise util.Abort(_("a bookmark of this name does not exist"))
61 raise util.Abort(_("a bookmark of this name does not exist"))
62 if mark in marks and not force:
62 if mark in marks and not force:
63 raise util.Abort(_("a bookmark of the same name already exists"))
63 raise util.Abort(_("a bookmark of the same name already exists"))
64 if mark is None:
64 if mark is None:
65 raise util.Abort(_("new bookmark name required"))
65 raise util.Abort(_("new bookmark name required"))
66 marks[mark] = marks[rename]
66 marks[mark] = marks[rename]
67 del marks[rename]
67 del marks[rename]
68 if repo._bookmarkcurrent == rename:
68 if repo._bookmarkcurrent == rename:
69 bookmarks.setcurrent(repo, mark)
69 bookmarks.setcurrent(repo, mark)
70 bookmarks.write(repo)
70 bookmarks.write(repo)
71 return
71 return
72
72
73 if delete:
73 if delete:
74 if mark is None:
74 if mark is None:
75 raise util.Abort(_("bookmark name required"))
75 raise util.Abort(_("bookmark name required"))
76 if mark not in marks:
76 if mark not in marks:
77 raise util.Abort(_("a bookmark of this name does not exist"))
77 raise util.Abort(_("a bookmark of this name does not exist"))
78 if mark == repo._bookmarkcurrent:
78 if mark == repo._bookmarkcurrent:
79 bookmarks.setcurrent(repo, None)
79 bookmarks.setcurrent(repo, None)
80 del marks[mark]
80 del marks[mark]
81 bookmarks.write(repo)
81 bookmarks.write(repo)
82 return
82 return
83
83
84 if mark is not None:
84 if mark is not None:
85 if "\n" in mark:
85 if "\n" in mark:
86 raise util.Abort(_("bookmark name cannot contain newlines"))
86 raise util.Abort(_("bookmark name cannot contain newlines"))
87 mark = mark.strip()
87 mark = mark.strip()
88 if not mark:
88 if not mark:
89 raise util.Abort(_("bookmark names cannot consist entirely of "
89 raise util.Abort(_("bookmark names cannot consist entirely of "
90 "whitespace"))
90 "whitespace"))
91 if mark in marks and not force:
91 if mark in marks and not force:
92 raise util.Abort(_("a bookmark of the same name already exists"))
92 raise util.Abort(_("a bookmark of the same name already exists"))
93 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
93 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
94 and not force):
94 and not force):
95 raise util.Abort(
95 raise util.Abort(
96 _("a bookmark cannot have the name of an existing branch"))
96 _("a bookmark cannot have the name of an existing branch"))
97 if rev:
97 if rev:
98 marks[mark] = repo.lookup(rev)
98 marks[mark] = repo.lookup(rev)
99 else:
99 else:
100 marks[mark] = repo.changectx('.').node()
100 marks[mark] = repo.changectx('.').node()
101 bookmarks.setcurrent(repo, mark)
101 bookmarks.setcurrent(repo, mark)
102 bookmarks.write(repo)
102 bookmarks.write(repo)
103 return
103 return
104
104
105 if mark is None:
105 if mark is None:
106 if rev:
106 if rev:
107 raise util.Abort(_("bookmark name required"))
107 raise util.Abort(_("bookmark name required"))
108 if len(marks) == 0:
108 if len(marks) == 0:
109 ui.status(_("no bookmarks set\n"))
109 ui.status(_("no bookmarks set\n"))
110 else:
110 else:
111 for bmark, n in marks.iteritems():
111 for bmark, n in marks.iteritems():
112 if ui.configbool('bookmarks', 'track.current'):
112 if ui.configbool('bookmarks', 'track.current'):
113 current = repo._bookmarkcurrent
113 current = repo._bookmarkcurrent
114 if bmark == current and n == cur:
114 if bmark == current and n == cur:
115 prefix, label = '*', 'bookmarks.current'
115 prefix, label = '*', 'bookmarks.current'
116 else:
116 else:
117 prefix, label = ' ', ''
117 prefix, label = ' ', ''
118 else:
118 else:
119 if n == cur:
119 if n == cur:
120 prefix, label = '*', 'bookmarks.current'
120 prefix, label = '*', 'bookmarks.current'
121 else:
121 else:
122 prefix, label = ' ', ''
122 prefix, label = ' ', ''
123
123
124 if ui.quiet:
124 if ui.quiet:
125 ui.write("%s\n" % bmark, label=label)
125 ui.write("%s\n" % bmark, label=label)
126 else:
126 else:
127 ui.write(" %s %-25s %d:%s\n" % (
127 ui.write(" %s %-25s %d:%s\n" % (
128 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
128 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
129 label=label)
129 label=label)
130 return
130 return
131
131
132 def _revstostrip(changelog, node):
132 def _revstostrip(changelog, node):
133 srev = changelog.rev(node)
133 srev = changelog.rev(node)
134 tostrip = [srev]
134 tostrip = [srev]
135 saveheads = []
135 saveheads = []
136 for r in xrange(srev, len(changelog)):
136 for r in xrange(srev, len(changelog)):
137 parents = changelog.parentrevs(r)
137 parents = changelog.parentrevs(r)
138 if parents[0] in tostrip or parents[1] in tostrip:
138 if parents[0] in tostrip or parents[1] in tostrip:
139 tostrip.append(r)
139 tostrip.append(r)
140 if parents[1] != nullrev:
140 if parents[1] != nullrev:
141 for p in parents:
141 for p in parents:
142 if p not in tostrip and p > srev:
142 if p not in tostrip and p > srev:
143 saveheads.append(p)
143 saveheads.append(p)
144 return [r for r in tostrip if r not in saveheads]
144 return [r for r in tostrip if r not in saveheads]
145
145
146 def strip(oldstrip, ui, repo, node, backup="all"):
146 def strip(oldstrip, ui, repo, node, backup="all"):
147 """Strip bookmarks if revisions are stripped using
147 """Strip bookmarks if revisions are stripped using
148 the mercurial.strip method. This usually happens during
148 the mercurial.strip method. This usually happens during
149 qpush and qpop"""
149 qpush and qpop"""
150 revisions = _revstostrip(repo.changelog, node)
150 revisions = _revstostrip(repo.changelog, node)
151 marks = repo._bookmarks
151 marks = repo._bookmarks
152 update = []
152 update = []
153 for mark, n in marks.iteritems():
153 for mark, n in marks.iteritems():
154 if repo.changelog.rev(n) in revisions:
154 if repo.changelog.rev(n) in revisions:
155 update.append(mark)
155 update.append(mark)
156 oldstrip(ui, repo, node, backup)
156 oldstrip(ui, repo, node, backup)
157 if len(update) > 0:
157 if len(update) > 0:
158 for m in update:
158 for m in update:
159 marks[m] = repo.changectx('.').node()
159 marks[m] = repo.changectx('.').node()
160 bookmarks.write(repo)
160 bookmarks.write(repo)
161
161
162 def reposetup(ui, repo):
162 def reposetup(ui, repo):
163 if not repo.local():
163 if not repo.local():
164 return
164 return
165
165
166 class bookmark_repo(repo.__class__):
166 class bookmark_repo(repo.__class__):
167 def lookup(self, key):
167 def lookup(self, key):
168 if key in self._bookmarks:
168 if key in self._bookmarks:
169 key = self._bookmarks[key]
169 key = self._bookmarks[key]
170 return super(bookmark_repo, self).lookup(key)
170 return super(bookmark_repo, self).lookup(key)
171
171
172 def pull(self, remote, heads=None, force=False):
172 def pull(self, remote, heads=None, force=False):
173 result = super(bookmark_repo, self).pull(remote, heads, force)
173 result = super(bookmark_repo, self).pull(remote, heads, force)
174
174
175 self.ui.debug("checking for updated bookmarks\n")
175 self.ui.debug("checking for updated bookmarks\n")
176 rb = remote.listkeys('bookmarks')
176 rb = remote.listkeys('bookmarks')
177 changed = False
177 changed = False
178 for k in rb.keys():
178 for k in rb.keys():
179 if k in self._bookmarks:
179 if k in self._bookmarks:
180 nr, nl = rb[k], self._bookmarks[k]
180 nr, nl = rb[k], self._bookmarks[k]
181 if nr in self:
181 if nr in self:
182 cr = self[nr]
182 cr = self[nr]
183 cl = self[nl]
183 cl = self[nl]
184 if cl.rev() >= cr.rev():
184 if cl.rev() >= cr.rev():
185 continue
185 continue
186 if cr in cl.descendants():
186 if cr in cl.descendants():
187 self._bookmarks[k] = cr.node()
187 self._bookmarks[k] = cr.node()
188 changed = True
188 changed = True
189 self.ui.status(_("updating bookmark %s\n") % k)
189 self.ui.status(_("updating bookmark %s\n") % k)
190 else:
190 else:
191 self.ui.warn(_("not updating divergent"
191 self.ui.warn(_("not updating divergent"
192 " bookmark %s\n") % k)
192 " bookmark %s\n") % k)
193 if changed:
193 if changed:
194 bookmarks.write(repo)
194 bookmarks.write(repo)
195
195
196 return result
196 return result
197
197
198 def push(self, remote, force=False, revs=None, newbranch=False):
198 def push(self, remote, force=False, revs=None, newbranch=False):
199 result = super(bookmark_repo, self).push(remote, force, revs,
199 result = super(bookmark_repo, self).push(remote, force, revs,
200 newbranch)
200 newbranch)
201
201
202 self.ui.debug("checking for updated bookmarks\n")
202 self.ui.debug("checking for updated bookmarks\n")
203 rb = remote.listkeys('bookmarks')
203 rb = remote.listkeys('bookmarks')
204 for k in rb.keys():
204 for k in rb.keys():
205 if k in self._bookmarks:
205 if k in self._bookmarks:
206 nr, nl = rb[k], hex(self._bookmarks[k])
206 nr, nl = rb[k], hex(self._bookmarks[k])
207 if nr in self:
207 if nr in self:
208 cr = self[nr]
208 cr = self[nr]
209 cl = self[nl]
209 cl = self[nl]
210 if cl in cr.descendants():
210 if cl in cr.descendants():
211 r = remote.pushkey('bookmarks', k, nr, nl)
211 r = remote.pushkey('bookmarks', k, nr, nl)
212 if r:
212 if r:
213 self.ui.status(_("updating bookmark %s\n") % k)
213 self.ui.status(_("updating bookmark %s\n") % k)
214 else:
214 else:
215 self.ui.warn(_('updating bookmark %s'
215 self.ui.warn(_('updating bookmark %s'
216 ' failed!\n') % k)
216 ' failed!\n') % k)
217
217
218 return result
218 return result
219
219
220 def addchangegroup(self, *args, **kwargs):
220 def addchangegroup(self, *args, **kwargs):
221 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
221 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
222 if result > 1:
222 if result > 1:
223 # We have more heads than before
223 # We have more heads than before
224 return result
224 return result
225 node = self.changelog.tip()
225 node = self.changelog.tip()
226 parents = self.dirstate.parents()
226 parents = self.dirstate.parents()
227 bookmarks.update(self, parents, node)
227 bookmarks.update(self, parents, node)
228 return result
228 return result
229
229
230 repo.__class__ = bookmark_repo
230 repo.__class__ = bookmark_repo
231
231
232 def pull(oldpull, ui, repo, source="default", **opts):
232 def pull(oldpull, ui, repo, source="default", **opts):
233 # translate bookmark args to rev args for actual pull
233 # translate bookmark args to rev args for actual pull
234 if opts.get('bookmark'):
234 if opts.get('bookmark'):
235 # this is an unpleasant hack as pull will do this internally
235 # this is an unpleasant hack as pull will do this internally
236 source, branches = hg.parseurl(ui.expandpath(source),
236 source, branches = hg.parseurl(ui.expandpath(source),
237 opts.get('branch'))
237 opts.get('branch'))
238 other = hg.repository(hg.remoteui(repo, opts), source)
238 other = hg.repository(hg.remoteui(repo, opts), source)
239 rb = other.listkeys('bookmarks')
239 rb = other.listkeys('bookmarks')
240
240
241 for b in opts['bookmark']:
241 for b in opts['bookmark']:
242 if b not in rb:
242 if b not in rb:
243 raise util.Abort(_('remote bookmark %s not found!') % b)
243 raise util.Abort(_('remote bookmark %s not found!') % b)
244 opts.setdefault('rev', []).append(b)
244 opts.setdefault('rev', []).append(b)
245
245
246 result = oldpull(ui, repo, source, **opts)
246 result = oldpull(ui, repo, source, **opts)
247
247
248 # update specified bookmarks
248 # update specified bookmarks
249 if opts.get('bookmark'):
249 if opts.get('bookmark'):
250 for b in opts['bookmark']:
250 for b in opts['bookmark']:
251 # explicit pull overrides local bookmark if any
251 # explicit pull overrides local bookmark if any
252 ui.status(_("importing bookmark %s\n") % b)
252 ui.status(_("importing bookmark %s\n") % b)
253 repo._bookmarks[b] = repo[rb[b]].node()
253 repo._bookmarks[b] = repo[rb[b]].node()
254 bookmarks.write(repo)
254 bookmarks.write(repo)
255
255
256 return result
256 return result
257
257
258 def push(oldpush, ui, repo, dest=None, **opts):
258 def push(oldpush, ui, repo, dest=None, **opts):
259 dopush = True
259 dopush = True
260 if opts.get('bookmark'):
260 if opts.get('bookmark'):
261 dopush = False
261 dopush = False
262 for b in opts['bookmark']:
262 for b in opts['bookmark']:
263 if b in repo._bookmarks:
263 if b in repo._bookmarks:
264 dopush = True
264 dopush = True
265 opts.setdefault('rev', []).append(b)
265 opts.setdefault('rev', []).append(b)
266
266
267 result = 0
267 result = 0
268 if dopush:
268 if dopush:
269 result = oldpush(ui, repo, dest, **opts)
269 result = oldpush(ui, repo, dest, **opts)
270
270
271 if opts.get('bookmark'):
271 if opts.get('bookmark'):
272 # this is an unpleasant hack as push will do this internally
272 # this is an unpleasant hack as push will do this internally
273 dest = ui.expandpath(dest or 'default-push', dest or 'default')
273 dest = ui.expandpath(dest or 'default-push', dest or 'default')
274 dest, branches = hg.parseurl(dest, opts.get('branch'))
274 dest, branches = hg.parseurl(dest, opts.get('branch'))
275 other = hg.repository(hg.remoteui(repo, opts), dest)
275 other = hg.repository(hg.remoteui(repo, opts), dest)
276 rb = other.listkeys('bookmarks')
276 rb = other.listkeys('bookmarks')
277 for b in opts['bookmark']:
277 for b in opts['bookmark']:
278 # explicit push overrides remote bookmark if any
278 # explicit push overrides remote bookmark if any
279 if b in repo._bookmarks:
279 if b in repo._bookmarks:
280 ui.status(_("exporting bookmark %s\n") % b)
280 ui.status(_("exporting bookmark %s\n") % b)
281 new = repo[b].hex()
281 new = repo[b].hex()
282 elif b in rb:
282 elif b in rb:
283 ui.status(_("deleting remote bookmark %s\n") % b)
283 ui.status(_("deleting remote bookmark %s\n") % b)
284 new = '' # delete
284 new = '' # delete
285 else:
285 else:
286 ui.warn(_('bookmark %s does not exist on the local '
286 ui.warn(_('bookmark %s does not exist on the local '
287 'or remote repository!\n') % b)
287 'or remote repository!\n') % b)
288 return 2
288 return 2
289 old = rb.get(b, '')
289 old = rb.get(b, '')
290 r = other.pushkey('bookmarks', b, old, new)
290 r = other.pushkey('bookmarks', b, old, new)
291 if not r:
291 if not r:
292 ui.warn(_('updating bookmark %s failed!\n') % b)
292 ui.warn(_('updating bookmark %s failed!\n') % b)
293 if not result:
293 if not result:
294 result = 2
294 result = 2
295
295
296 return result
296 return result
297
297
298 def incoming(oldincoming, ui, repo, source="default", **opts):
298 def incoming(oldincoming, ui, repo, source="default", **opts):
299 if opts.get('bookmarks'):
299 if opts.get('bookmarks'):
300 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
300 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
301 other = hg.repository(hg.remoteui(repo, opts), source)
301 other = hg.repository(hg.remoteui(repo, opts), source)
302 ui.status(_('comparing with %s\n') % url.hidepassword(source))
302 ui.status(_('comparing with %s\n') % url.hidepassword(source))
303 return bookmarks.diff(ui, repo, other)
303 return bookmarks.diff(ui, repo, other)
304 else:
304 else:
305 return oldincoming(ui, repo, source, **opts)
305 return oldincoming(ui, repo, source, **opts)
306
306
307 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
307 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
308 if opts.get('bookmarks'):
308 if opts.get('bookmarks'):
309 dest = ui.expandpath(dest or 'default-push', dest or 'default')
309 dest = ui.expandpath(dest or 'default-push', dest or 'default')
310 dest, branches = hg.parseurl(dest, opts.get('branch'))
310 dest, branches = hg.parseurl(dest, opts.get('branch'))
311 other = hg.repository(hg.remoteui(repo, opts), dest)
311 other = hg.repository(hg.remoteui(repo, opts), dest)
312 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
312 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
313 return bookmarks.diff(ui, other, repo)
313 return bookmarks.diff(ui, other, repo)
314 else:
314 else:
315 return oldoutgoing(ui, repo, dest, **opts)
315 return oldoutgoing(ui, repo, dest, **opts)
316
316
317 def uisetup(ui):
317 def uisetup(ui):
318 extensions.wrapfunction(repair, "strip", strip)
318 extensions.wrapfunction(repair, "strip", strip)
319 if ui.configbool('bookmarks', 'track.current'):
319 if ui.configbool('bookmarks', 'track.current'):
320 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
320 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
321
321
322 entry = extensions.wrapcommand(commands.table, 'pull', pull)
322 entry = extensions.wrapcommand(commands.table, 'pull', pull)
323 entry[1].append(('B', 'bookmark', [],
323 entry[1].append(('B', 'bookmark', [],
324 _("bookmark to import"),
324 _("bookmark to import"),
325 _('BOOKMARK')))
325 _('BOOKMARK')))
326 entry = extensions.wrapcommand(commands.table, 'push', push)
326 entry = extensions.wrapcommand(commands.table, 'push', push)
327 entry[1].append(('B', 'bookmark', [],
327 entry[1].append(('B', 'bookmark', [],
328 _("bookmark to export"),
328 _("bookmark to export"),
329 _('BOOKMARK')))
329 _('BOOKMARK')))
330 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
330 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
331 entry[1].append(('B', 'bookmarks', False,
331 entry[1].append(('B', 'bookmarks', False,
332 _("compare bookmark")))
332 _("compare bookmark")))
333 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
333 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
334 entry[1].append(('B', 'bookmarks', False,
334 entry[1].append(('B', 'bookmarks', False,
335 _("compare bookmark")))
335 _("compare bookmark")))
336
336
337 def updatecurbookmark(orig, ui, repo, *args, **opts):
337 def updatecurbookmark(orig, ui, repo, *args, **opts):
338 '''Set the current bookmark
338 '''Set the current bookmark
339
339
340 If the user updates to a bookmark we update the .hg/bookmarks.current
340 If the user updates to a bookmark we update the .hg/bookmarks.current
341 file.
341 file.
342 '''
342 '''
343 res = orig(ui, repo, *args, **opts)
343 res = orig(ui, repo, *args, **opts)
344 rev = opts['rev']
344 rev = opts['rev']
345 if not rev and len(args) > 0:
345 if not rev and len(args) > 0:
346 rev = args[0]
346 rev = args[0]
347 bookmarks.setcurrent(repo, rev)
347 bookmarks.setcurrent(repo, rev)
348 return res
348 return res
349
349
350 cmdtable = {
350 cmdtable = {
351 "bookmarks":
351 "bookmarks":
352 (bookmark,
352 (bookmark,
353 [('f', 'force', False, _('force')),
353 [('f', 'force', False, _('force')),
354 ('r', 'rev', '', _('revision'), _('REV')),
354 ('r', 'rev', '', _('revision'), _('REV')),
355 ('d', 'delete', False, _('delete a given bookmark')),
355 ('d', 'delete', False, _('delete a given bookmark')),
356 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
356 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
357 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
357 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
358 }
358 }
359
360 colortable = {'bookmarks.current': 'green'}
@@ -1,319 +1,320 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for the status and qseries commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
4 #
5 # This program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 '''colorize output from some commands
19 '''colorize output from some commands
20
20
21 This extension modifies the status and resolve commands to add color to their
21 This extension modifies the status and resolve commands to add color to their
22 output to reflect file status, the qseries command to add color to reflect
22 output to reflect file status, the qseries command to add color to reflect
23 patch status (applied, unapplied, missing), and to diff-related
23 patch status (applied, unapplied, missing), and to diff-related
24 commands to highlight additions, removals, diff headers, and trailing
24 commands to highlight additions, removals, diff headers, and trailing
25 whitespace.
25 whitespace.
26
26
27 Other effects in addition to color, like bold and underlined text, are
27 Other effects in addition to color, like bold and underlined text, are
28 also available. Effects are rendered with the ECMA-48 SGR control
28 also available. Effects are rendered with the ECMA-48 SGR control
29 function (aka ANSI escape codes). This module also provides the
29 function (aka ANSI escape codes). This module also provides the
30 render_text function, which can be used to add effects to any text.
30 render_text function, which can be used to add effects to any text.
31
31
32 Default effects may be overridden from your configuration file::
32 Default effects may be overridden from your configuration file::
33
33
34 [color]
34 [color]
35 status.modified = blue bold underline red_background
35 status.modified = blue bold underline red_background
36 status.added = green bold
36 status.added = green bold
37 status.removed = red bold blue_background
37 status.removed = red bold blue_background
38 status.deleted = cyan bold underline
38 status.deleted = cyan bold underline
39 status.unknown = magenta bold underline
39 status.unknown = magenta bold underline
40 status.ignored = black bold
40 status.ignored = black bold
41
41
42 # 'none' turns off all effects
42 # 'none' turns off all effects
43 status.clean = none
43 status.clean = none
44 status.copied = none
44 status.copied = none
45
45
46 qseries.applied = blue bold underline
46 qseries.applied = blue bold underline
47 qseries.unapplied = black bold
47 qseries.unapplied = black bold
48 qseries.missing = red bold
48 qseries.missing = red bold
49
49
50 diff.diffline = bold
50 diff.diffline = bold
51 diff.extended = cyan bold
51 diff.extended = cyan bold
52 diff.file_a = red bold
52 diff.file_a = red bold
53 diff.file_b = green bold
53 diff.file_b = green bold
54 diff.hunk = magenta
54 diff.hunk = magenta
55 diff.deleted = red
55 diff.deleted = red
56 diff.inserted = green
56 diff.inserted = green
57 diff.changed = white
57 diff.changed = white
58 diff.trailingwhitespace = bold red_background
58 diff.trailingwhitespace = bold red_background
59
59
60 resolve.unresolved = red bold
60 resolve.unresolved = red bold
61 resolve.resolved = green bold
61 resolve.resolved = green bold
62
62
63 bookmarks.current = green
63 bookmarks.current = green
64
64
65 branches.active = none
65 branches.active = none
66 branches.closed = black bold
66 branches.closed = black bold
67 branches.current = green
67 branches.current = green
68 branches.inactive = none
68 branches.inactive = none
69
69
70 The color extension will try to detect whether to use ANSI codes or
70 The color extension will try to detect whether to use ANSI codes or
71 Win32 console APIs, unless it is made explicit::
71 Win32 console APIs, unless it is made explicit::
72
72
73 [color]
73 [color]
74 mode = ansi
74 mode = ansi
75
75
76 Any value other than 'ansi', 'win32', or 'auto' will disable color.
76 Any value other than 'ansi', 'win32', or 'auto' will disable color.
77
77
78 '''
78 '''
79
79
80 import os
80 import os
81
81
82 from mercurial import commands, dispatch, extensions, ui as uimod, util
82 from mercurial import commands, dispatch, extensions, ui as uimod, util
83 from mercurial.i18n import _
83 from mercurial.i18n import _
84
84
85 # start and stop parameters for effects
85 # start and stop parameters for effects
86 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
86 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
87 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
87 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
88 'italic': 3, 'underline': 4, 'inverse': 7,
88 'italic': 3, 'underline': 4, 'inverse': 7,
89 'black_background': 40, 'red_background': 41,
89 'black_background': 40, 'red_background': 41,
90 'green_background': 42, 'yellow_background': 43,
90 'green_background': 42, 'yellow_background': 43,
91 'blue_background': 44, 'purple_background': 45,
91 'blue_background': 44, 'purple_background': 45,
92 'cyan_background': 46, 'white_background': 47}
92 'cyan_background': 46, 'white_background': 47}
93
93
94 _styles = {'grep.match': 'red bold',
94 _styles = {'grep.match': 'red bold',
95 'bookmarks.current': 'green',
95 'branches.active': 'none',
96 'branches.active': 'none',
96 'branches.closed': 'black bold',
97 'branches.closed': 'black bold',
97 'branches.current': 'green',
98 'branches.current': 'green',
98 'branches.inactive': 'none',
99 'branches.inactive': 'none',
99 'diff.changed': 'white',
100 'diff.changed': 'white',
100 'diff.deleted': 'red',
101 'diff.deleted': 'red',
101 'diff.diffline': 'bold',
102 'diff.diffline': 'bold',
102 'diff.extended': 'cyan bold',
103 'diff.extended': 'cyan bold',
103 'diff.file_a': 'red bold',
104 'diff.file_a': 'red bold',
104 'diff.file_b': 'green bold',
105 'diff.file_b': 'green bold',
105 'diff.hunk': 'magenta',
106 'diff.hunk': 'magenta',
106 'diff.inserted': 'green',
107 'diff.inserted': 'green',
107 'diff.trailingwhitespace': 'bold red_background',
108 'diff.trailingwhitespace': 'bold red_background',
108 'diffstat.deleted': 'red',
109 'diffstat.deleted': 'red',
109 'diffstat.inserted': 'green',
110 'diffstat.inserted': 'green',
110 'log.changeset': 'yellow',
111 'log.changeset': 'yellow',
111 'resolve.resolved': 'green bold',
112 'resolve.resolved': 'green bold',
112 'resolve.unresolved': 'red bold',
113 'resolve.unresolved': 'red bold',
113 'status.added': 'green bold',
114 'status.added': 'green bold',
114 'status.clean': 'none',
115 'status.clean': 'none',
115 'status.copied': 'none',
116 'status.copied': 'none',
116 'status.deleted': 'cyan bold underline',
117 'status.deleted': 'cyan bold underline',
117 'status.ignored': 'black bold',
118 'status.ignored': 'black bold',
118 'status.modified': 'blue bold',
119 'status.modified': 'blue bold',
119 'status.removed': 'red bold',
120 'status.removed': 'red bold',
120 'status.unknown': 'magenta bold underline'}
121 'status.unknown': 'magenta bold underline'}
121
122
122
123
123 def render_effects(text, effects):
124 def render_effects(text, effects):
124 'Wrap text in commands to turn on each effect.'
125 'Wrap text in commands to turn on each effect.'
125 if not text:
126 if not text:
126 return text
127 return text
127 start = [str(_effects[e]) for e in ['none'] + effects.split()]
128 start = [str(_effects[e]) for e in ['none'] + effects.split()]
128 start = '\033[' + ';'.join(start) + 'm'
129 start = '\033[' + ';'.join(start) + 'm'
129 stop = '\033[' + str(_effects['none']) + 'm'
130 stop = '\033[' + str(_effects['none']) + 'm'
130 return ''.join([start, text, stop])
131 return ''.join([start, text, stop])
131
132
132 def extstyles():
133 def extstyles():
133 for name, ext in extensions.extensions():
134 for name, ext in extensions.extensions():
134 _styles.update(getattr(ext, 'colortable', {}))
135 _styles.update(getattr(ext, 'colortable', {}))
135
136
136 def configstyles(ui):
137 def configstyles(ui):
137 for status, cfgeffects in ui.configitems('color'):
138 for status, cfgeffects in ui.configitems('color'):
138 if '.' not in status:
139 if '.' not in status:
139 continue
140 continue
140 cfgeffects = ui.configlist('color', status)
141 cfgeffects = ui.configlist('color', status)
141 if cfgeffects:
142 if cfgeffects:
142 good = []
143 good = []
143 for e in cfgeffects:
144 for e in cfgeffects:
144 if e in _effects:
145 if e in _effects:
145 good.append(e)
146 good.append(e)
146 else:
147 else:
147 ui.warn(_("ignoring unknown color/effect %r "
148 ui.warn(_("ignoring unknown color/effect %r "
148 "(configured in color.%s)\n")
149 "(configured in color.%s)\n")
149 % (e, status))
150 % (e, status))
150 _styles[status] = ' '.join(good)
151 _styles[status] = ' '.join(good)
151
152
152 class colorui(uimod.ui):
153 class colorui(uimod.ui):
153 def popbuffer(self, labeled=False):
154 def popbuffer(self, labeled=False):
154 if labeled:
155 if labeled:
155 return ''.join(self.label(a, label) for a, label
156 return ''.join(self.label(a, label) for a, label
156 in self._buffers.pop())
157 in self._buffers.pop())
157 return ''.join(a for a, label in self._buffers.pop())
158 return ''.join(a for a, label in self._buffers.pop())
158
159
159 _colormode = 'ansi'
160 _colormode = 'ansi'
160 def write(self, *args, **opts):
161 def write(self, *args, **opts):
161 label = opts.get('label', '')
162 label = opts.get('label', '')
162 if self._buffers:
163 if self._buffers:
163 self._buffers[-1].extend([(str(a), label) for a in args])
164 self._buffers[-1].extend([(str(a), label) for a in args])
164 elif self._colormode == 'win32':
165 elif self._colormode == 'win32':
165 for a in args:
166 for a in args:
166 win32print(a, super(colorui, self).write, **opts)
167 win32print(a, super(colorui, self).write, **opts)
167 else:
168 else:
168 return super(colorui, self).write(
169 return super(colorui, self).write(
169 *[self.label(str(a), label) for a in args], **opts)
170 *[self.label(str(a), label) for a in args], **opts)
170
171
171 def write_err(self, *args, **opts):
172 def write_err(self, *args, **opts):
172 label = opts.get('label', '')
173 label = opts.get('label', '')
173 if self._colormode == 'win32':
174 if self._colormode == 'win32':
174 for a in args:
175 for a in args:
175 win32print(a, super(colorui, self).write_err, **opts)
176 win32print(a, super(colorui, self).write_err, **opts)
176 else:
177 else:
177 return super(colorui, self).write_err(
178 return super(colorui, self).write_err(
178 *[self.label(str(a), label) for a in args], **opts)
179 *[self.label(str(a), label) for a in args], **opts)
179
180
180 def label(self, msg, label):
181 def label(self, msg, label):
181 effects = []
182 effects = []
182 for l in label.split():
183 for l in label.split():
183 s = _styles.get(l, '')
184 s = _styles.get(l, '')
184 if s:
185 if s:
185 effects.append(s)
186 effects.append(s)
186 effects = ''.join(effects)
187 effects = ''.join(effects)
187 if effects:
188 if effects:
188 return '\n'.join([render_effects(s, effects)
189 return '\n'.join([render_effects(s, effects)
189 for s in msg.split('\n')])
190 for s in msg.split('\n')])
190 return msg
191 return msg
191
192
192
193
193 def uisetup(ui):
194 def uisetup(ui):
194 if ui.plain():
195 if ui.plain():
195 return
196 return
196 mode = ui.config('color', 'mode', 'auto')
197 mode = ui.config('color', 'mode', 'auto')
197 if mode == 'auto':
198 if mode == 'auto':
198 if os.name == 'nt' and 'TERM' not in os.environ:
199 if os.name == 'nt' and 'TERM' not in os.environ:
199 # looks line a cmd.exe console, use win32 API or nothing
200 # looks line a cmd.exe console, use win32 API or nothing
200 mode = w32effects and 'win32' or 'none'
201 mode = w32effects and 'win32' or 'none'
201 else:
202 else:
202 mode = 'ansi'
203 mode = 'ansi'
203 if mode == 'win32':
204 if mode == 'win32':
204 if w32effects is None:
205 if w32effects is None:
205 # only warn if color.mode is explicitly set to win32
206 # only warn if color.mode is explicitly set to win32
206 ui.warn(_('win32console not found, please install pywin32\n'))
207 ui.warn(_('win32console not found, please install pywin32\n'))
207 return
208 return
208 _effects.update(w32effects)
209 _effects.update(w32effects)
209 elif mode != 'ansi':
210 elif mode != 'ansi':
210 return
211 return
211 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
212 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
212 coloropt = opts['color']
213 coloropt = opts['color']
213 auto = coloropt == 'auto'
214 auto = coloropt == 'auto'
214 always = util.parsebool(coloropt)
215 always = util.parsebool(coloropt)
215 if (always or
216 if (always or
216 (always is None and
217 (always is None and
217 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
218 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
218 colorui._colormode = mode
219 colorui._colormode = mode
219 colorui.__bases__ = (ui_.__class__,)
220 colorui.__bases__ = (ui_.__class__,)
220 ui_.__class__ = colorui
221 ui_.__class__ = colorui
221 extstyles()
222 extstyles()
222 configstyles(ui_)
223 configstyles(ui_)
223 return orig(ui_, opts, cmd, cmdfunc)
224 return orig(ui_, opts, cmd, cmdfunc)
224 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
225 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
225
226
226 def extsetup(ui):
227 def extsetup(ui):
227 commands.globalopts.append(
228 commands.globalopts.append(
228 ('', 'color', 'auto',
229 ('', 'color', 'auto',
229 # i18n: 'always', 'auto', and 'never' are keywords and should
230 # i18n: 'always', 'auto', and 'never' are keywords and should
230 # not be translated
231 # not be translated
231 _("when to colorize (boolean, always, auto, or never)"),
232 _("when to colorize (boolean, always, auto, or never)"),
232 _('TYPE')))
233 _('TYPE')))
233
234
234 try:
235 try:
235 import re, pywintypes, win32console as win32c
236 import re, pywintypes, win32console as win32c
236
237
237 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
238 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
238 w32effects = {
239 w32effects = {
239 'none': -1,
240 'none': -1,
240 'black': 0,
241 'black': 0,
241 'red': win32c.FOREGROUND_RED,
242 'red': win32c.FOREGROUND_RED,
242 'green': win32c.FOREGROUND_GREEN,
243 'green': win32c.FOREGROUND_GREEN,
243 'yellow': win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN,
244 'yellow': win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN,
244 'blue': win32c.FOREGROUND_BLUE,
245 'blue': win32c.FOREGROUND_BLUE,
245 'magenta': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_RED,
246 'magenta': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_RED,
246 'cyan': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_GREEN,
247 'cyan': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_GREEN,
247 'white': (win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN |
248 'white': (win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN |
248 win32c.FOREGROUND_BLUE),
249 win32c.FOREGROUND_BLUE),
249 'bold': win32c.FOREGROUND_INTENSITY,
250 'bold': win32c.FOREGROUND_INTENSITY,
250 'black_background': 0x100, # unused value > 0x0f
251 'black_background': 0x100, # unused value > 0x0f
251 'red_background': win32c.BACKGROUND_RED,
252 'red_background': win32c.BACKGROUND_RED,
252 'green_background': win32c.BACKGROUND_GREEN,
253 'green_background': win32c.BACKGROUND_GREEN,
253 'yellow_background': win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN,
254 'yellow_background': win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN,
254 'blue_background': win32c.BACKGROUND_BLUE,
255 'blue_background': win32c.BACKGROUND_BLUE,
255 'purple_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_RED,
256 'purple_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_RED,
256 'cyan_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_GREEN,
257 'cyan_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_GREEN,
257 'white_background': (win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN |
258 'white_background': (win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN |
258 win32c.BACKGROUND_BLUE),
259 win32c.BACKGROUND_BLUE),
259 'bold_background': win32c.BACKGROUND_INTENSITY,
260 'bold_background': win32c.BACKGROUND_INTENSITY,
260 'underline': win32c.COMMON_LVB_UNDERSCORE, # double-byte charsets only
261 'underline': win32c.COMMON_LVB_UNDERSCORE, # double-byte charsets only
261 'inverse': win32c.COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
262 'inverse': win32c.COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
262 }
263 }
263
264
264 passthrough = set([win32c.FOREGROUND_INTENSITY,
265 passthrough = set([win32c.FOREGROUND_INTENSITY,
265 win32c.BACKGROUND_INTENSITY,
266 win32c.BACKGROUND_INTENSITY,
266 win32c.COMMON_LVB_UNDERSCORE,
267 win32c.COMMON_LVB_UNDERSCORE,
267 win32c.COMMON_LVB_REVERSE_VIDEO])
268 win32c.COMMON_LVB_REVERSE_VIDEO])
268
269
269 try:
270 try:
270 stdout = win32c.GetStdHandle(win32c.STD_OUTPUT_HANDLE)
271 stdout = win32c.GetStdHandle(win32c.STD_OUTPUT_HANDLE)
271 if stdout is None:
272 if stdout is None:
272 raise ImportError()
273 raise ImportError()
273 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
274 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
274 except pywintypes.error:
275 except pywintypes.error:
275 # stdout may be defined but not support
276 # stdout may be defined but not support
276 # GetConsoleScreenBufferInfo(), when called from subprocess or
277 # GetConsoleScreenBufferInfo(), when called from subprocess or
277 # redirected.
278 # redirected.
278 raise ImportError()
279 raise ImportError()
279 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
280 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
280
281
281 def win32print(text, orig, **opts):
282 def win32print(text, orig, **opts):
282 label = opts.get('label', '')
283 label = opts.get('label', '')
283 attr = origattr
284 attr = origattr
284
285
285 def mapcolor(val, attr):
286 def mapcolor(val, attr):
286 if val == -1:
287 if val == -1:
287 return origattr
288 return origattr
288 elif val in passthrough:
289 elif val in passthrough:
289 return attr | val
290 return attr | val
290 elif val > 0x0f:
291 elif val > 0x0f:
291 return (val & 0x70) | (attr & 0x8f)
292 return (val & 0x70) | (attr & 0x8f)
292 else:
293 else:
293 return (val & 0x07) | (attr & 0xf8)
294 return (val & 0x07) | (attr & 0xf8)
294
295
295 # determine console attributes based on labels
296 # determine console attributes based on labels
296 for l in label.split():
297 for l in label.split():
297 style = _styles.get(l, '')
298 style = _styles.get(l, '')
298 for effect in style.split():
299 for effect in style.split():
299 attr = mapcolor(w32effects[effect], attr)
300 attr = mapcolor(w32effects[effect], attr)
300
301
301 # hack to ensure regexp finds data
302 # hack to ensure regexp finds data
302 if not text.startswith('\033['):
303 if not text.startswith('\033['):
303 text = '\033[m' + text
304 text = '\033[m' + text
304
305
305 # Look for ANSI-like codes embedded in text
306 # Look for ANSI-like codes embedded in text
306 m = re.match(ansire, text)
307 m = re.match(ansire, text)
307 while m:
308 while m:
308 for sattr in m.group(1).split(';'):
309 for sattr in m.group(1).split(';'):
309 if sattr:
310 if sattr:
310 attr = mapcolor(int(sattr), attr)
311 attr = mapcolor(int(sattr), attr)
311 stdout.SetConsoleTextAttribute(attr)
312 stdout.SetConsoleTextAttribute(attr)
312 orig(m.group(2), **opts)
313 orig(m.group(2), **opts)
313 m = re.match(ansire, m.group(3))
314 m = re.match(ansire, m.group(3))
314
315
315 # Explicity reset original attributes
316 # Explicity reset original attributes
316 stdout.SetConsoleTextAttribute(origattr)
317 stdout.SetConsoleTextAttribute(origattr)
317
318
318 except ImportError:
319 except ImportError:
319 w32effects = None
320 w32effects = None
General Comments 0
You need to be logged in to leave comments. Login now