diff --git a/crmsh/command.py b/crmsh/command.py index 35b91425c..77ff717fa 100644 --- a/crmsh/command.py +++ b/crmsh/command.py @@ -8,6 +8,8 @@ import inspect import re +import ctypes +import ctypes.util from . import help as help_module from . import ui_utils from . import log @@ -566,6 +568,10 @@ def complete(self, context, args): ''' ret = [] if self.completer is not None: + if sort_completion_inst is not None and sort_completion_inst.value == 0: + # Restore the original value of rl_sort_completion_matches + # before calling the completer again + sort_completion_inst.value = orig_sort_completion_value specs = inspect.getfullargspec(self.completer) if 'context' in specs.args: ret = self.completer([self.name] + args, context) @@ -591,3 +597,16 @@ def _check_args(fn, expected): if argnames != expected: raise ValueError(fn.__name__ + ": Expected method with signature " + repr(expected)) + + +readline_path = ctypes.util.find_library('readline') +sort_completion_inst = None +if readline_path: + # Inspired by + # https://stackoverflow.com/questions/31229708/python-gnu-readline-bindings-keep-my-sort-order + # Python readline module sort the completion result by + # alphabetical order by default. To archive the custom + # sort order, need to disable the sort_completion_matches + rl = ctypes.cdll.LoadLibrary(readline_path) + sort_completion_inst = ctypes.c_ulong.in_dll(rl, 'rl_sort_completion_matches') + orig_sort_completion_value = sort_completion_inst.value diff --git a/crmsh/ra.py b/crmsh/ra.py index f5bbdf4f2..17c9c9d4a 100644 --- a/crmsh/ra.py +++ b/crmsh/ra.py @@ -377,7 +377,7 @@ def mk_ra_node(self): self.broken_ra = False return self.ra_elem - def params(self, completion=False): + def params(self): ''' Construct a dict of dicts: parameters are keys and dictionary of attributes/values are values. Cached too. @@ -385,12 +385,6 @@ def params(self, completion=False): completion: If true, filter some (advanced) parameters out. ''' - if completion: - if self.mk_ra_node() is None: - return None - return [c.get("name") - for c in self.ra_elem.xpath("//parameters/parameter") - if c.get("name") and c.get("name") not in self.excluded_from_completion] ident = "ra_params-%s" % self if cache.is_cached(ident): return cache.retrieve(ident) @@ -399,7 +393,7 @@ def params(self, completion=False): d = {} for c in self.ra_elem.xpath("//parameters/parameter"): name = c.get("name") - if not name: + if not name or name in self.excluded_from_completion: continue required = c.get("required") if not (c.get("deprecated") or c.get("obsoletes")) else "0" unique = c.get("unique") @@ -410,6 +404,10 @@ def params(self, completion=False): "type": typ, "default": default, } + items = list(d.items()) + # Sort the dictionary by required and then alphabetically + items.sort(key=lambda item: (item[1]["required"] != '1', item[0])) + d = dict(items) return cache.store(ident, d) def actions(self): diff --git a/crmsh/ui_configure.py b/crmsh/ui_configure.py index 7915eb23c..39e71d302 100644 --- a/crmsh/ui_configure.py +++ b/crmsh/ui_configure.py @@ -231,7 +231,10 @@ def _prim_params_completer(agent, args): return [] elif '=' in completing: return [] - return utils.filter_keys(agent.params(completion=True), args) + if command.sort_completion_inst is not None: + # Set the value to 0 to use the costum sort order + command.sort_completion_inst.value = 0 + return utils.filter_keys(agent.params(), args) def _prim_meta_completer(agent, args): diff --git a/crmsh/ui_context.py b/crmsh/ui_context.py index c655059aa..0b66e79e4 100644 --- a/crmsh/ui_context.py +++ b/crmsh/ui_context.py @@ -229,7 +229,7 @@ def matching(word): ret = None # logging.debug("line:%s, text:%s, ret:%s, state:%s", repr(line), repr(text), ret, state) if not text or (ret and line.split()[-1].endswith(ret)): - if ret == "id=": + if ret.endswith('='): return ret return ret + ' ' return ret