[PATCH [REBASED]] settings: add 'Tools' panel

96 views
Skip to first unread message

Angel Ezquerra

unread,
Apr 28, 2012, 2:31:33 PM4/28/12
to thg...@googlegroups.com
# HG changeset patch
# User Angel Ezquerra <angel.e...@gmail.com>
# Date 1333456876 -7200
# Branch stable
# Node ID 0dd296db8c936a761206415b4c1c948e3a102d73
# Parent cb6330ef213ae7671e806692f3dead760d1fb2a3
settings: add 'Tools' panel

Add a new panel to the settings dialog which allows configuring (adding,
editing, deleting and reordering) TortoiseHg custom tools.

diff --git a/tortoisehg/hgqt/customtoolconfig.py b/tortoisehg/hgqt/customtoolconfig.py
new file mode 100644
--- /dev/null
+++ b/tortoisehg/hgqt/customtoolconfig.py
@@ -0,0 +1,179 @@
+# customtoolconfig.py - Configuration dialog for TortoiseHg custom tools
+#
+# Copyright 2012 Angel Ezquerra <angel.e...@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2, incorporated herein by reference.
+
+from tortoisehg.hgqt.i18n import _
+from tortoisehg.hgqt import qtlib
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+class CustomToolConfigDialog(QDialog):
+ 'Dialog for editing the a custom tool configuration'
+
+ _enablemappings = {'All items': 'istrue',
+ 'Working Directory': 'iswd',
+ 'All revisions': 'isrev',
+ 'All contexts': 'isctx',
+ 'Fixed revisions': 'fixed',
+ 'Applied patches': 'applied',
+ 'qgoto': 'qgoto'}
+
+ _locationmappings = {'Everywhere': 'workbench, repowidget',
+ 'Workbench Toolbar': 'workbench',
+ 'Selected Revision context menu': 'repowidget',}
+
+ def __init__(self, parent=None, toolname=None, toolconfig={}):
+ QDialog.__init__(self, parent)
+
+ self.setWindowIcon(qtlib.geticon('tools-spanner-hammer'))
+ self.setWindowTitle('Configure Custom Tool')
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
+
+ self.hbox = QHBoxLayout()
+ vbox = QVBoxLayout()
+
+ command = toolconfig.get('command', '')
+ label = toolconfig.get('label', '')
+ tooltip = toolconfig.get('tooltip', '')
+ ico = toolconfig.get('icon', '')
+ location = toolconfig.get('location', 'workbench repowidget')
+ enable = toolconfig.get('enable', 'all')
+ outputlog = toolconfig.get('outputlog', 'False')
+
+ self.name = self._addConfigItem(vbox, _('Tool name'),
+ QLineEdit(toolname), _('The tool name. It cannot contain spaces.'))
+ # Execute a mercurial command. These _MUST_ start with "hg"
+ self.command = self._addConfigItem(vbox, _('Command'),
+ QLineEdit(command), _('The command that will be executed.\n'
+ 'To execute a mercurial command use "hg" (rather than "hg.exe") '
+ 'as the executable command.\n'
+ 'You can use {ROOT} as an alias of the current repository root and\n'
+ '{REV} as an alias of the selected revision.'))
+ self.label = self._addConfigItem(vbox, _('Tool label'),
+ QLineEdit(label),
+ _('The tool label, which is what will be shown '
+ 'on the repowidget context menu.\n'
+ 'If no label is set, the tool name will be used as the tool label.\n'
+ 'If no tooltip is set, the label will be used as the tooltip as well.'))
+ self.tooltip = self._addConfigItem(vbox, _('Tooltip'),
+ QLineEdit(tooltip),
+ _('The tooltip that will be shown on the tool button.\n'
+ 'This is only shown when the tool button is shown on\n'
+ 'the workbench toolbar.'))
+ self.icon = self._addConfigItem(vbox, _('Icon'),
+ QLineEdit(ico),
+ _('The tool icon.\n'
+ 'You can use any built-in TortoiseHg icon\n'
+ 'by setting this value to a vaild TortoiseHg icon name\n'
+ '(e.g. clone, add, remove, sync, thg-logo, hg-update, etc).\n'
+ 'You can also set this value to the absoluate path to\n'
+ 'any icon on your file system.'))
+
+ combo = self._genCombo(self._locationmappings.keys(),
+ self._location2label(location), 'Everywhere')
+ self.location = self._addConfigItem(vbox, _('Show command in'), combo,
+ _('The location where you want to show'
+ 'the command icon or menu entry:\n'
+ '- Workbench: Show a button on the Workbench Custom toolbar.\n'
+ '- Repowidget: Show an entry on the selected revision context menu.'))
+
+ combo = self._genCombo(self._enablemappings.keys(),
+ self._enable2label(enable), 'All items')
+ self.enable = self._addConfigItem(vbox, _('On repowidget, show for'),
+ combo, _('For which kinds of revisions the tool will be enabled\n'
+ 'It is only taken into account when the tool is shown on the\n'
+ 'selected revision context menu.'))
+
+ combo = self._genCombo(('True', 'False'), outputlog)
+ self.showoutput = self._addConfigItem(vbox, _('Show Output Log'),
+ combo, _('When enabled, automatically show the Output Log when the'
+ 'command is run.\nDefault: False.'))
+
+ self.hbox.addLayout(vbox)
+ vbox = QVBoxLayout()
+ self.okbutton = QPushButton(_('OK'))
+ self.okbutton.clicked.connect(self.okClicked)
+ vbox.addWidget(self.okbutton)
+ self.cancelbutton = QPushButton(_('Cancel'))
+ self.cancelbutton.clicked.connect(self.reject)
+ vbox.addWidget(self.cancelbutton)
+ vbox.addStretch()
+ self.hbox.addLayout(vbox)
+ self.setLayout(self.hbox)
+
+ def value(self):
+ toolname = str(self.name.text()).strip()
+ toolconfig = {
+ 'label': str(self.label.text()),
+ 'command': str(self.command.text()),
+ 'tooltip': str(self.tooltip.text()),
+ 'icon': str(self.icon.text()),
+ 'location': self._locationmappings[str(self.location.currentText())],
+ 'enable': self._enablemappings[str(self.enable.currentText())],
+ 'showoutput': str(self.showoutput.currentText()),
+ }
+ return toolname, toolconfig
+
+ def _genCombo(self, items, selecteditem=None, defaultitem=None):
+ print items
+ index = 0
+ if selecteditem:
+ print 'selecteditem: %s' % selecteditem
+ try:
+ index = items.index(selecteditem)
+ print index
+ except:
+ if defaultitem:
+ try:
+ print 'defaultitem: %s' % defaultitem
+ index = items.index(defaultitem)
+ except:
+ print 'not found'
+ pass
+ combo = QComboBox()
+ combo.addItems(items)
+ if index:
+ combo.setCurrentIndex(index)
+ return combo
+
+ def _addConfigItem(self, parent, label, configwidget, tooltip=None):
+ if tooltip:
+ configwidget.setToolTip(tooltip)
+ hbox = QHBoxLayout()
+ hbox.addWidget(QLabel(label))
+ hbox.addWidget(configwidget)
+ parent.addLayout(hbox)
+ return configwidget
+
+ def _enable2label(self, label):
+ return self._dictvalue2key(self._enablemappings, label)
+
+ def _location2label(self, label):
+ return self._dictvalue2key(self._locationmappings, label)
+
+ def _dictvalue2key(self, dictionary, value):
+ for key in dictionary:
+ if value == dictionary[key]:
+ return key
+ return None
+
+ def okClicked(self):
+ errormsg = self.validateForm()
+ if errormsg:
+ qtlib.WarningMsgBox(_('Missing information'), errormsg)
+ return
+ return self.accept()
+
+ def validateForm(self):
+ name, config = self.value()
+ if not name:
+ return _('You must set a tool name.')
+ if name.find(' ') >= 0:
+ return _('The tool name cannot have any spaces in it.')
+ if not config['command']:
+ return _('You must set a command to run.')
+ return '' # No error
diff --git a/tortoisehg/hgqt/settings.py b/tortoisehg/hgqt/settings.py
--- a/tortoisehg/hgqt/settings.py
+++ b/tortoisehg/hgqt/settings.py
@@ -11,7 +11,7 @@

from tortoisehg.util import hglib, settings, paths, wconfig, i18n, bugtraq
from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import qtlib, qscilib, thgrepo
+from tortoisehg.hgqt import qtlib, qscilib, thgrepo, customtoolconfig

from PyQt4.QtCore import *
from PyQt4.QtGui import *
@@ -180,6 +180,7 @@
self.curvalue = None
self.setEchoMode(QLineEdit.Password)
self.setMinimumWidth(ENTRY_WIDTH)
+
class TextEntry(QTextEdit):
def __init__(self, parent=None, **opts):
QTextEdit.__init__(self, parent, toolTip=opts['tooltip'])
@@ -307,6 +308,103 @@
return self.value() != self.curvalue


+class ListBox(QListWidget):
+ def __init__(self, parent=None, **opts):
+ QListWidget.__init__(self, parent, **opts)
+ self.opts = opts
+ self.curvalue = None
+ self.setMinimumWidth(ENTRY_WIDTH)
+
+ def values(self):
+ out = []
+ for row in range(self.count()):
+ out.append(self.item(row).text())
+ return out
+
+ ## common APIs for all edit widgets
+ def setValue(self, curvalue):
+ self.curvalue = curvalue
+
+ def value(self):
+ row = self.currentIndex().row()
+ if row < 0:
+ return None
+ else:
+ return self.item(row).text()
+
+ def isDirty(self):
+ return self.value() != self.curvalue
+
+
+class ToolListBox(ListBox):
+ def __init__(self, parent=None, **opts):
+ ListBox.__init__(self, parent, **opts)
+
+ self.curvalue = (None, None)
+ self.tortoisehgtools, self.toolnames = hglib.tortoisehgtools(ui.ui())
+ self.addItems(self.toolnames)
+
+ self.doubleClicked.connect(self.editToolItem)
+
+ # Enable drag and drop to reorder the tools
+ self.setDragEnabled(True)
+ self.setDragDropMode(self.InternalMove)
+ self.setDefaultDropAction(Qt.MoveAction)
+
+ def takeItem(self, p_int):
+ item = self.item(p_int)
+ itemtext = str(item.text())
+ del self.tortoisehgtools[itemtext]
+ print self.tortoisehgtools
+ super(ToolListBox, self).takeItem(p_int)
+
+ def addTool(self):
+ td = customtoolconfig.CustomToolConfigDialog(self)
+ res = td.exec_()
+ if res:
+ row = self.currentIndex().row()
+ toolname, toolconfig = td.value()
+ if row < 0:
+ self.addItem(toolname)
+ self.setCurrentRow(self.count()-1)
+ else:
+ self.insertItem(row+1, toolname)
+ self.setCurrentRow(row+1)
+ self.tortoisehgtools[toolname] = toolconfig
+
+ def editTool(self, row=None):
+ if row is None:
+ row = self.currentIndex().row()
+ if row < 0:
+ return self.addTool()
+ else:
+ item = self.item(row)
+ toolname = item.text()
+ td = customtoolconfig.CustomToolConfigDialog(
+ self, toolname=toolname,
+ toolconfig=self.tortoisehgtools[str(toolname)])
+ res = td.exec_()
+ if res:
+ toolname, toolconfig = td.value()
+ self.takeItem(row)
+ self.insertItem(row, toolname)
+ self.setCurrentRow(row)
+ self.tortoisehgtools[toolname] = toolconfig
+
+ def editToolItem(self, item):
+ self.editTool(item.row())
+
+ def deleteTool(self, row=None):
+ if row is None:
+ row = self.currentIndex().row()
+ if row >= 0:
+ self.takeItem(row)
+
+ ## common APIs for all edit widgets
+ def value(self):
+ return (self.tortoisehgtools, self.values())
+
+
class BugTraqConfigureEntry(QPushButton):
def __init__(self, parent=None, **opts):
QPushButton.__init__(self, parent, toolTip=opts['tooltip'])
@@ -866,6 +964,9 @@
({'name': 'extensions', 'label': _('Extensions'), 'icon': 'hg-extensions'}, (
)),

+({'name': 'tools', 'label': _('Tools'), 'icon': 'tools-spanner-hammer'}, (
+ )),
+
({'name': 'issue', 'label': _('Issue Tracking'), 'icon': 'edit-file'}, (
_fi(_('Issue Regex'), 'tortoisehg.issue.regex', genEditCombo,
_('Defines the regex to match when picking up issue numbers.')),
@@ -1240,6 +1341,9 @@
# make sure widgets are shown properly,
# even when no extensions mentioned in the config file
self.validateextensions()
+ elif name == 'tools':
+ self.tortoisehgtools, self.toolnames = hglib.tortoisehgtools(ui.ui())
+ widgets[0].setValue((self.tortoisehgtools, self.toolnames))
else:
for row, e in enumerate(info):
curvalue = self.readCPath(e.cpath)
@@ -1333,6 +1437,32 @@
widgets.append(w)
return extsinfo, widgets

+ def fillToolsFrame(self):
+ widgets = []
+ frame = QFrame()
+ hbox = QHBoxLayout()
+ frame.setLayout(hbox)
+ self.stack.addWidget(frame)
+
+ toollist = ToolListBox()
+ hbox.addWidget(toollist)
+ vbox = QVBoxLayout()
+
+ addbutton = QPushButton(_('Add'), self)
+ addbutton.clicked.connect(toollist.addTool)
+ editbutton = QPushButton(_('Edit'), self)
+ editbutton.clicked.connect(lambda: toollist.editTool(None))
+ deletebutton = QPushButton(_('Delete'), self)
+ deletebutton.clicked.connect(lambda: toollist.deleteTool(None))
+ vbox.addWidget(addbutton)
+ vbox.addWidget(editbutton)
+ vbox.addWidget(deletebutton)
+ vbox.addStretch()
+ hbox.addLayout(vbox)
+ widgets.append(toollist)
+ toolsinfo = ()
+ return toolsinfo, widgets
+
def eventFilter(self, obj, event):
if event.type() in (QEvent.Enter, QEvent.FocusIn):
self.desctext.setHtml(obj.toolTip())
@@ -1350,6 +1480,9 @@
if name == 'extensions':
extsinfo, widgets = self.fillExtensionsFrame()
self.pages[name] = name, extsinfo, widgets
+ elif name == 'tools':
+ toolsinfo, widgets = self.fillToolsFrame()
+ self.pages[name] = name, toolsinfo, widgets
else:
widgets = self.fillFrame(info)
self.pages[name] = name, info, widgets
@@ -1407,6 +1540,8 @@
for name, info, widgets in self.pages.values():
if name == 'extensions':
self.applyChangesForExtensions()
+ elif name == 'tools':
+ self.applyChangesForTools()
else:
for row, e in enumerate(info):
newvalue = widgets[row].value()
@@ -1482,6 +1617,41 @@
invalmsg = invalmsg.decode('utf-8')
chk.setToolTip(invalmsg or hglib.tounicode(allexts[name]))

+ def applyChangesForTools(self):
+ emitChanged = False
+ toollist = self.pages['tools'][2][0]
+ if not toollist.isDirty():
+ return
+
+ tools, toolnames = toollist.value()
+ curtools, curtoolnames = toollist.curvalue
+
+ # In order to keep the tool order we must delete all existing
+ # custom tool configurations, and then set all the configuration
+ # settings anew:
+ section = 'tortoisehg-tools'
+
+ # 1. Remove the existing tool configurations
+ fieldnames = ('command', 'label', 'tooltip',
+ 'icon', 'location', 'enable', 'showoutput',)
+ for name in curtoolnames:
+ for field in fieldnames:
+ try:
+ keyname = '%s.%s' % (name, field)
+ del self.ini[section][keyname]
+ except KeyError:
+ pass
+
+ # 2. Configure the new configurations
+ for uname in toolnames:
+ name = hglib.fromunicode(uname)
+ for field in sorted(tools[name]):
+ keyname = '%s.%s' % (name, field)
+ value = tools[name][field]
+ if not value is '':
+ self.ini.set(section, keyname, value)
+
+ self.restartRequested.emit(_('Tools'))

def run(ui, *pats, **opts):
return SettingsDialog(opts.get('alias') == 'repoconfig',

Yuya Nishihara

unread,
Apr 30, 2012, 6:41:48 AM4/30/12
to thg...@googlegroups.com
missing _()

> + self.setWindowFlags(self.windowFlags()& ~Qt.WindowContextHelpButtonHint)
A small thing, "except ValueError" ?
(and debug prints)
It needs to update current selection accordingly.
It isn't recommended to override non-virtual function provided by C++ layer.
(and extra debug print here :)
ui.ui() returns merged values of both user and repository settings.
So we cannot rely on it in settings dialog.

Also, it looks some variables of ToolListBox loaded separately:

- this setValue() updates only `curvalue`
- `tortoisehgtools`, `toolnames` are loaded at __init__()
- and value() returns `(tortoisehgtools, toolnames)`

Isn't it a bit strange?
emitChanged isn't used.
extra "is ''" ? or "== ''" ?

Angel Ezquerra

unread,
Apr 30, 2012, 7:13:02 AM4/30/12
to thg...@googlegroups.com


On Apr 30, 2012 12:41 PM, "Yuya Nishihara" <yu...@tcha.org> wrote:
>
> On 04/29/2012 03:31 AM, Angel Ezquerra wrote:
>>
>> # HG changeset patch
>> # User Angel Ezquerra<angel.e...@gmail.com>
>> # Date 1333456876 -7200
>> # Branch stable
>> # Node ID 0dd296db8c936a761206415b4c1c948e3a102d73
>> # Parent  cb6330ef213ae7671e806692f3dead760d1fb2a3
>> settings: add 'Tools' panel
>>
>> Add a new panel to the settings dialog which allows configuring (adding,
>> editing, deleting and reordering) TortoiseHg custom tools.
>>

...

>> +        if selecteditem:
>> +            print 'selecteditem: %s' % selecteditem
>> +            try:
>> +                index = items.index(selecteditem)
>> +                print index
>> +            except:
>> +                if defaultitem:
>> +                    try:
>> +                        print 'defaultitem: %s' % defaultitem
>> +                        index = items.index(defaultitem)
>> +                    except:
>> +                        print 'not found'
>> +                        pass
>
>
> A small thing, "except ValueError" ?
> (and debug prints)

I I've got this bad habit of not specifying the exception type when I believe its obvious that only one type of exception can be thrown. I must stop doing that!

Also, I should create a commit hook that warns me about print statements our something  :-P

I didn't know that. I just followed the examples on other parts of the code.

Ummm. What would be the best way to proceed here then? I want to update the tortoisehgtools dict when an item is removed from the widget.

> (and extra debug print here :)

:-P

Actually I'm not too happy that I'm relaying on the ui.config methods at all. They return dicts, which do not prevent the item order as you know. I want the tools to appear on a predictable order. Using dicts makes this hard to do. I wish I could use ordereddicts, but I don't think python 2.4 sports them.

Any suggestions?

> Also, it looks some variables of ToolListBox loaded separately:
>
>  - this setValue() updates only `curvalue`
>  - `tortoisehgtools`, `toolnames` are loaded at __init__()
>  - and value() returns `(tortoisehgtools, toolnames)`
>
> Isn't it a bit strange?

when I originally wrote this code it made sense to me, but I'll look into that again (I'm replying from my phone)

Cheers, and thanks for the thorough review!

Angel

Yuya Nishihara

unread,
Apr 30, 2012, 11:11:39 AM4/30/12
to thg...@googlegroups.com
On 04/30/2012 08:13 PM, Angel Ezquerra wrote:
...
For example, have ListBox manage only list of tool names, and keep
tools dict separately?

>>> + elif name == 'tools':
>>> + self.tortoisehgtools, self.toolnames =
> hglib.tortoisehgtools(ui.ui())
>>> + widgets[0].setValue((self.tortoisehgtools, self.toolnames))
>>
>>
>> ui.ui() returns merged values of both user and repository settings.
>> So we cannot rely on it in settings dialog.
>
> Actually I'm not too happy that I'm relaying on the ui.config methods at
> all. They return dicts, which do not prevent the item order as you know. I
> want the tools to appear on a predictable order. Using dicts makes this
> hard to do. I wish I could use ordereddicts, but I don't think python 2.4
> sports them.
>
> Any suggestions?

Hmm, Mercurial's config object (and our wconfig) tries to keep definition
order:

http://selenic.com/repo/hg/file/be786c5ac0a8/mercurial/config.py#l12

If not, possibly you found a bug.

And yes, ordereddict is only available on Python 2.6 or later.

Regards,

Angel Ezquerra

unread,
Apr 30, 2012, 12:00:19 PM4/30/12
to thg...@googlegroups.com

These methods ensure that the last value that is set for a given configuration key is the one that is taken into account. But they work with dicts. As far as I know there is no way to retrieve different keys in the order they were set, is there?

Angel

Yuya Nishihara

unread,
May 2, 2012, 3:58:56 AM5/2/12
to thg...@googlegroups.com
.items() or iterator should preserve the order. Isn't it sufficient?

In [2]: cfg = config.config()
In [3]: cfg.read('.hgrc')
In [16]: cfg['tortoisehg-tools'].items()
Out[16]:
[('update_to_tip.command', 'hg update tip'),
('update_to_tip.icon', 'hg-update'),
('update_to_tip.showoutput', 'True'),
('update_to_tip.tooltip', 'Update to tip'),
('update_to_nil.command', 'hg update null'),
('update_to_nil.enable', 'istrue'),
('update_to_nil.icon', 'hg-update'),
('update_to_nil.location', 'workbench repowidget'),
('update_to_nil.showoutput', 'False'),
('update_to_nil.tooltip', 'Update to null')]
In [18]: list(cfg['tortoisehg-tools']) # calls __iter__()
Out[18]:
['update_to_tip.command',
'update_to_tip.icon',
'update_to_tip.showoutput',
'update_to_tip.tooltip',
'update_to_nil.command',
'update_to_nil.enable',
'update_to_nil.icon',
'update_to_nil.location',
'update_to_nil.showoutput',
'update_to_nil.tooltip']

Angel Ezquerra

unread,
May 2, 2012, 4:05:32 AM5/2/12
to thg...@googlegroups.com
Yuya, cfg is a regular python dictionary in this example, is it not?

In that case, this is what Python's 2.7.2 documention has to say about
its items() method
(http://docs.python.org/library/stdtypes.html#dictionary-view-objects):

<quote>
items()

Return a copy of the dictionary’s list of (key, value) pairs.

CPython implementation detail: Keys and values are listed in an
arbitrary order which is non-random, varies across Python
implementations, and depends on the dictionary’s history of insertions
and deletions.
</quote>

So I think it is unwise to rely on the item order to correspond to the
order in which items were added to the dict.

Cheers,

Angel

P.S.- Did you get a chance to have a look at the couple of small
changes to the repowidget context menu that I sent a few days ago?

Yuya Nishihara

unread,
May 2, 2012, 4:31:56 AM5/2/12
to thg...@googlegroups.com
It's a sortdict object:

In [7]: type(cfg)
Out[7]: mercurial.config.config

In [8]: type(cfg['tortoisehg-tools'])
Out[8]: mercurial.config.sortdict

And some methods of sortdict keeps insertion order by using extra _list attribute:

In [9]: cfg['tortoisehg-tools']._list
Out[9]:
['update_to_tip.command',
...

> In that case, this is what Python's 2.7.2 documention has to say about
> its items() method
> (http://docs.python.org/library/stdtypes.html#dictionary-view-objects):
>
> <quote>
> items()
>
> Return a copy of the dictionary�s list of (key, value) pairs.
>
> CPython implementation detail: Keys and values are listed in an
> arbitrary order which is non-random, varies across Python
> implementations, and depends on the dictionary�s history of insertions
> and deletions.
> </quote>
>
> So I think it is unwise to rely on the item order to correspond to the
> order in which items were added to the dict.
>
> Cheers,
>
> Angel
>
> P.S.- Did you get a chance to have a look at the couple of small
> changes to the repowidget context menu that I sent a few days ago?

Sorry, I did't read these mails yet. I'll check them later.

Regards,

Angel Ezquerra

unread,
May 2, 2012, 4:39:26 AM5/2/12
to thg...@googlegroups.com
That is awesome. It will make things much simpler then! :-D

Thanks for the info,

Angel

Angel Ezquerra

unread,
May 6, 2012, 1:02:09 PM5/6/12
to thg...@googlegroups.com
Yuya,

I've been looking into your suggestion but I am unsure how can I use
it. I have several problems:

1. ui.configitems() returns a list of tuples, with every tuple
representing an entry on the section being read. The order of the list
items is random (i.e. it does not follow the order in which each item
was declared on the hgrc files). This makes me believe that a regular
python dictionary is being used internally by ui.configitems.

2. Even if I use a mercurial.confing.config object, when I do:

type(cfg['tortoisehg-tools'])

I get:

<type 'dict'>

rather than:

mercurial.config.sortdict()

What is going on?

Angel

Yuya Nishihara

unread,
May 7, 2012, 10:26:12 AM5/7/12
to thg...@googlegroups.com
If it's a normal dict, ui.configitems() won't preserve the original order.
Could you send me a patch to reproduce the problem? I cannot find the culprit.

Regards,

Angel Ezquerra

unread,
May 7, 2012, 10:29:07 AM5/7/12
to thg...@googlegroups.com
Yuya, what do you mean? do you want me to resend the "settings: add
tools panel" patch again?

Cheers,

Angel

Yuya Nishihara

unread,
May 7, 2012, 10:34:58 AM5/7/12
to thg...@googlegroups.com
No, I want your test code, in which config dict is a dict.

There may be some code path to change sortdict to dict, but I cannot find
where it is.

Regards,

Angel Ezquerra

unread,
May 7, 2012, 10:43:11 AM5/7/12
to thg...@googlegroups.com
I believe I simply added the following to hglib.tortoisehgtools():

from mercurial import config
cfg = config.config()
cfg.read('.hg/hgrc')
print type(cfg)
print type(cfg['tortoieshg-tools'])

to the top of the function.

The result printed:

<class 'mercurial.config.config'>
<type 'dict'>

while I expected it to print:
<class 'mercurial.config.config'>
<type 'mercurial.config.sortdict'>

Cheers,

Angel

Yuya Nishihara

unread,
May 7, 2012, 11:12:35 AM5/7/12
to thg...@googlegroups.com
Does '.hg/hgrc' contain [tortoisehg-tools] section?
If not, cfg['tortoisehg-tools'] will return empty dict.

Angel Ezquerra

unread,
May 7, 2012, 11:31:03 AM5/7/12
to thg...@googlegroups.com
You are right. That does work. As you said it returns a regular python
dict if the section is not found.

Is there a way to do something akin to ui.configitems, but which
returns a sortdict instead?

Using mecurial.config.config() requires knowing the path of the hgrc
file that you want to read, and instead I'd like to read all the hgrc
files as mercurial does, in the order that mercurial follows, but get
a sortdict...

Angel

Yuya Nishihara

unread,
May 7, 2012, 12:27:46 PM5/7/12
to thg...@googlegroups.com
Are you trying to use sortdict out of config or ui module?
I think sortdict is designed for internal use, as it lacks keys() or iteritems().

Angel Ezquerra

unread,
May 7, 2012, 6:04:08 PM5/7/12
to thg...@googlegroups.com
What I want is to get the tools defined in the tortoisehg-tools
section in the order in which they are set in that section. The
ui.config methods do not let you do that because they return regular
python objects. I did not know about the mercurial.config.sortdict or
the mercurial.config.config. When you mentioned it it seemed to me
that I may use those for my purpose?

Angel

Yuya Nishihara

unread,
May 8, 2012, 8:18:45 AM5/8/12
to thg...@googlegroups.com
On 05/08/2012 07:04 AM, Angel Ezquerra wrote:
> On Mon, May 7, 2012 at 6:27 PM, Yuya Nishihara<yu...@tcha.org> wrote:
[...]
ui.configitems() should preserve the order. Doesn't it work?

Angel Ezquerra

unread,
May 11, 2012, 5:10:55 PM5/11/12
to thg...@googlegroups.com
This is very weird. If I run:

ui.configitems('tortoisehg-tools')

from mercurial itself (by running hg --debugger and then executing the
command at the debug prompt) I get the list of config items in the
same order that they are on my config file.

However, if I put:

print ui.configitems('tortoisehg-tools')

inside hglib.tortoisehgtools(), I get them in a different order!

I get a list of items which are not in the same order I put them on my
mercurial.ini file.

I am a bit stuck with this patch. I'd like to be able to preserve the
tool ordering. Otherwise each time you edit the tool list you may end
up with a different tool order...

Angel

Yuya Nishihara

unread,
May 12, 2012, 7:44:26 AM5/12/12
to thg...@googlegroups.com
um, very strange. Just in case, could you test it in fresh environment?
You can switch profile by HGRCPATH and APPDATA environment variable.

If any repository's .hgrc defines [tortoisehg-tools] section and if it's
listed in thg-reporegistry.xml, it may happen to override user config.

Angel Ezquerra

unread,
May 12, 2012, 8:47:10 PM5/12/12
to thg...@googlegroups.com
I don't think that is the problem.

I did the following:

1. add the following code to the top of hglib.tortoisehgtools():

print '-'
print type(ui)
print ui.configitems('tortoisehg-tools')
print '-'

2. Remove the tortoisehg-tools section from my mercurial.ini file
(located on my windows profile folder)

3. Run thg with: C:\Python26\python.exe thg --nofork --newworkbench

I got the following output:

-
<class 'mercurial.ui.ui'>
[]
-
-
<class 'mercurial.ui.ui'>
[]
-
-
<class 'mercurial.ui.ui'>
[]
-
-
<class 'mercurial.ui.ui'>
[]
-
-
<class 'mercurial.ui.ui'>
[]
-

This means that there is no other tortoisehg-tools section on any
other hgrc file (which I also verified anyway).

I then added the tortoisehg-tools section back. Its contents are:

[tortoisehg-tools]
# Execute a mercurial command. These _MUST_ start with "hg"
# Note that we can use any built-in TortoiseHg icon
update_to_tip.command = hg update -r tip
update_to_tip.enable = istrue
update_to_tip.location = repowidget
update_to_tip.showoutput = False
explore_wd.command = explorer.exe /e,{ROOT}
explore_wd.enable = iswd
explore_wd.label = Open in explorer
explore_wd.location = repowidget
explore_wd.showoutput = True
update_to_1.command = hg update 1
update_to_1.icon = hg-update
update_to_1.location = repowidget
update_to_1.showoutput = True
update_to_1.tooltip = Update to 1
name.command = command
name.enable = istrue
name.label = label
name.location = workbench repowidget
name.showoutput = True
name.tooltip = tooltip


Finally I ran thg again:

C:\Users\Angel\Documents\Repositories\thg>C:\Python26\python.exe thg --nofork --
newworkbench
QWindowsFileSystemWatcherEngine: unknown message '
-
<class 'mercurial.ui.ui'>
[('update_to_tip.command', 'hg update -r tip'), ('update_to_tip.enable', 'istrue
'), ('update_to_tip.location', 'repowidget'), ('update_to_tip.showoutput', 'Fals
e'), ('explore_wd.command', 'explorer.exe /e,{ROOT}'), ('explore_wd.enable', 'is
wd'), ('explore_wd.label', 'Open in explorer'), ('explore_wd.location', 'repowid
get'), ('explore_wd.showoutput', 'True'), ('update_to_1.command', 'hg update 1')
, ('update_to_1.icon', 'hg-update'), ('update_to_1.location', 'repowidget'), ('u
pdate_to_1.showoutput', 'True'), ('update_to_1.tooltip', 'Update to 1'), ('name.
command', 'command'), ('name.enable', 'istrue'), ('name.label', 'label'), ('name
.location', 'workbench repowidget'), ('name.showoutput', 'True'), ('name.tooltip
', 'tooltip')]
-
-
<class 'mercurial.ui.ui'>
[('explore_wd.command', 'explorer.exe /e,{ROOT}'), ('explore_wd.enable', 'iswd')
, ('explore_wd.label', 'Open in explorer'), ('explore_wd.location', 'repowidget'
), ('explore_wd.showoutput', 'True'), ('name.command', 'command'), ('name.enable
', 'istrue'), ('name.label', 'label'), ('name.location', 'workbench repowidget')
, ('name.showoutput', 'True'), ('name.tooltip', 'tooltip'), ('update_to_1.comman
d', 'hg update 1'), ('update_to_1.icon', 'hg-update'), ('update_to_1.location',
'repowidget'), ('update_to_1.showoutput', 'True'), ('update_to_1.tooltip', 'Upda
te to 1'), ('update_to_tip.command', 'hg update -r tip'), ('update_to_tip.enable
', 'istrue'), ('update_to_tip.location', 'repowidget'), ('update_to_tip.showoutp
ut', 'False')]
-
-
<class 'mercurial.ui.ui'>
[('explore_wd.command', 'explorer.exe /e,{ROOT}'), ('explore_wd.enable', 'iswd')
, ('explore_wd.label', 'Open in explorer'), ('explore_wd.location', 'repowidget'
), ('explore_wd.showoutput', 'True'), ('name.command', 'command'), ('name.enable
', 'istrue'), ('name.label', 'label'), ('name.location', 'workbench repowidget')
, ('name.showoutput', 'True'), ('name.tooltip', 'tooltip'), ('update_to_1.comman
d', 'hg update 1'), ('update_to_1.icon', 'hg-update'), ('update_to_1.location',
'repowidget'), ('update_to_1.showoutput', 'True'), ('update_to_1.tooltip', 'Upda
te to 1'), ('update_to_tip.command', 'hg update -r tip'), ('update_to_tip.enable
', 'istrue'), ('update_to_tip.location', 'repowidget'), ('update_to_tip.showoutp
ut', 'False')]
-
QWindowsFileSystemWatcherEngine: unknown message '
-
<class 'mercurial.ui.ui'>
[('explore_wd.command', 'explorer.exe /e,{ROOT}'), ('explore_wd.enable', 'iswd')
, ('explore_wd.label', 'Open in explorer'), ('explore_wd.location', 'repowidget'
), ('explore_wd.showoutput', 'True'), ('name.command', 'command'), ('name.enable
', 'istrue'), ('name.label', 'label'), ('name.location', 'workbench repowidget')
, ('name.showoutput', 'True'), ('name.tooltip', 'tooltip'), ('update_to_1.comman
d', 'hg update 1'), ('update_to_1.icon', 'hg-update'), ('update_to_1.location',
'repowidget'), ('update_to_1.showoutput', 'True'), ('update_to_1.tooltip', 'Upda
te to 1'), ('update_to_tip.command', 'hg update -r tip'), ('update_to_tip.enable
', 'istrue'), ('update_to_tip.location', 'repowidget'), ('update_to_tip.showoutp
ut', 'False')]
-
-
<class 'mercurial.ui.ui'>
[('explore_wd.command', 'explorer.exe /e,{ROOT}'), ('explore_wd.enable', 'iswd')
, ('explore_wd.label', 'Open in explorer'), ('explore_wd.location', 'repowidget'
), ('explore_wd.showoutput', 'True'), ('name.command', 'command'), ('name.enable
', 'istrue'), ('name.label', 'label'), ('name.location', 'workbench repowidget')
, ('name.showoutput', 'True'), ('name.tooltip', 'tooltip'), ('update_to_1.comman
d', 'hg update 1'), ('update_to_1.icon', 'hg-update'), ('update_to_1.location',
'repowidget'), ('update_to_1.showoutput', 'True'), ('update_to_1.tooltip', 'Upda
te to 1'), ('update_to_tip.command', 'hg update -r tip'), ('update_to_tip.enable
', 'istrue'), ('update_to_tip.location', 'repowidget'), ('update_to_tip.showoutp
ut', 'False')]
-

As you can see hglib.tortoisehgtools is called multiple times during
the TortoiseHg startup. The first time the order is correct! However
the second time the function is called (and all the other times) the
order is wrong!

Looking at the hglib.tortoisehgtools() method there seems to be no
catching which could explain the problem. It is probably something
really simple, but it is quite late here and I faile to see it now :-)

Cheers,

Angel

Yuya Nishihara

unread,
May 13, 2012, 1:19:00 AM5/13/12
to thg...@googlegroups.com
On 05/13/2012 09:47 AM, Angel Ezquerra wrote:
> On Sat, May 12, 2012 at 1:44 PM, Yuya Nishihara<yu...@tcha.org> wrote:
>> On 05/12/2012 06:10 AM, Angel Ezquerra wrote:
>>>> ui.configitems() should preserve the order. Doesn't it work?
>>>
>>> This is very weird. If I run:
>>>
>>> ui.configitems('tortoisehg-tools')
>>>
>>> from mercurial itself (by running hg --debugger and then executing the
>>> command at the debug prompt) I get the list of config items in the
>>> same order that they are on my config file.
>>>
>>> However, if I put:
>>>
>>> print ui.configitems('tortoisehg-tools')
>>>
>>> inside hglib.tortoisehgtools(), I get them in a different order!
>>>
>>> I get a list of items which are not in the same order I put them on my
>>> mercurial.ini file.
>>>
>>> I am a bit stuck with this patch. I'd like to be able to preserve the
>>> tool ordering. Otherwise each time you edit the tool list you may end
>>> up with a different tool order...
>>
>>
>> um, very strange. Just in case, could you test it in fresh environment?
>> You can switch profile by HGRCPATH and APPDATA environment variable.
>>
>> If any repository's .hgrc defines [tortoisehg-tools] section and if it's
>> listed in thg-reporegistry.xml, it may happen to override user config.
>
> I don't think that is the problem.

[...]
[...]

> -
> <class 'mercurial.ui.ui'>
> [('explore_wd.command', 'explorer.exe /e,{ROOT}'), ('explore_wd.enable', 'iswd')
> , ('explore_wd.label', 'Open in explorer'), ('explore_wd.location', 'repowidget'
> ), ('explore_wd.showoutput', 'True'), ('name.command', 'command'), ('name.enable
> ', 'istrue'), ('name.label', 'label'), ('name.location', 'workbench repowidget')
> , ('name.showoutput', 'True'), ('name.tooltip', 'tooltip'), ('update_to_1.comman
> d', 'hg update 1'), ('update_to_1.icon', 'hg-update'), ('update_to_1.location',
> 'repowidget'), ('update_to_1.showoutput', 'True'), ('update_to_1.tooltip', 'Upda
> te to 1'), ('update_to_tip.command', 'hg update -r tip'), ('update_to_tip.enable
> ', 'istrue'), ('update_to_tip.location', 'repowidget'), ('update_to_tip.showoutp
> ut', 'False')]
> -
>
> As you can see hglib.tortoisehgtools is called multiple times during
> the TortoiseHg startup. The first time the order is correct! However
> the second time the function is called (and all the other times) the
> order is wrong!
>
> Looking at the hglib.tortoisehgtools() method there seems to be no
> catching which could explain the problem. It is probably something
> really simple, but it is quite late here and I faile to see it now :-)

Got it. Maybe you are using projrc extension?
It reorders items alphabetically:
https://bitbucket.org/aragost/projrc/src/a9ccbead5335/projrc.py#cl-286

Angel Ezquerra

unread,
May 13, 2012, 2:32:08 AM5/13/12
to thg...@googlegroups.com
Yes I am. Good catch!

I got write rights on the projrc repo, so I may even push the fix that
you proposed to Martin :-)

In the meantime I can disable the extension and use the
ui.configitems() as it was intended. I thought that ui.configitems()
returned the items in random order because behind the scenes mercurial
was using dicts. In retrospects it makes sense that mercurial would
preserve the order. It seems that the projrc extension was messing
with me the whole time! :-P

Now I can try to finish this patch once and for all! :-D

Angel
Reply all
Reply to author
Forward
0 new messages