##// END OF EJS Templates
Use environment variable to identify conda / mamba (#14515)...
Use environment variable to identify conda / mamba (#14515) Conda and mamba both set an environment variable which refers to the base environment's executable path, use that in preference to less reliable methods, but fall back on the other approaches if unable to locate the executable this way. Additionally, change the search to look for the bare command name rather than the command within the top level of the active environment, I'm dubious this approach works with any current conda / mamba version which usually place their executable links in a `condabin` directory or elsewhere not at the same level as the Python executable. I believe this will also address https://github.com/ipython/ipython/issues/14350, which I'm also seeing in a Windows context where the regex fails to parse and causes a traceback.

File last commit:

r28293:704639d5
r28871:d23bee0c merge
Show More
autogen_shortcuts.py
223 lines | 6.6 KiB | text/x-python | PythonLexer
/ docs / autogen_shortcuts.py
from dataclasses import dataclass
from inspect import getsource
from pathlib import Path
from typing import cast, List, Union
from html import escape as html_escape
import re
from prompt_toolkit.keys import KEY_ALIASES
from prompt_toolkit.key_binding import KeyBindingsBase
from prompt_toolkit.filters import Filter, Condition
from prompt_toolkit.shortcuts import PromptSession
from IPython.terminal.shortcuts import create_ipython_shortcuts, create_identifier
from IPython.terminal.shortcuts.filters import KEYBINDING_FILTERS
@dataclass
class Shortcut:
#: a sequence of keys (each element on the list corresponds to pressing one or more keys)
keys_sequence: List[str]
filter: str
@dataclass
class Handler:
description: str
identifier: str
@dataclass
class Binding:
handler: Handler
shortcut: Shortcut
class _NestedFilter(Filter):
"""Protocol reflecting non-public prompt_toolkit's `_AndList` and `_OrList`."""
filters: List[Filter]
class _Invert(Filter):
"""Protocol reflecting non-public prompt_toolkit's `_Invert`."""
filter: Filter
conjunctions_labels = {"_AndList": "&", "_OrList": "|"}
ATOMIC_CLASSES = {"Never", "Always", "Condition"}
HUMAN_NAMES_FOR_FILTERS = {
filter_: name for name, filter_ in KEYBINDING_FILTERS.items()
}
def format_filter(
filter_: Union[Filter, _NestedFilter, Condition, _Invert],
is_top_level=True,
skip=None,
) -> str:
"""Create easily readable description of the filter."""
s = filter_.__class__.__name__
if s == "Condition":
func = cast(Condition, filter_).func
if filter_ in HUMAN_NAMES_FOR_FILTERS:
return HUMAN_NAMES_FOR_FILTERS[filter_]
name = func.__name__
if name == "<lambda>":
source = getsource(func)
return source.split("=")[0].strip()
return func.__name__
elif s == "_Invert":
operand = cast(_Invert, filter_).filter
if operand.__class__.__name__ in ATOMIC_CLASSES:
return f"~{format_filter(operand, is_top_level=False)}"
return f"~({format_filter(operand, is_top_level=False)})"
elif s in conjunctions_labels:
filters = cast(_NestedFilter, filter_).filters
if filter_ in HUMAN_NAMES_FOR_FILTERS:
return HUMAN_NAMES_FOR_FILTERS[filter_]
conjunction = conjunctions_labels[s]
glue = f" {conjunction} "
result = glue.join(format_filter(x, is_top_level=False) for x in filters)
if len(filters) > 1 and not is_top_level:
result = f"({result})"
return result
elif s in ["Never", "Always"]:
return s.lower()
elif s == "PassThrough":
return "pass_through"
else:
raise ValueError(f"Unknown filter type: {filter_}")
def sentencize(s) -> str:
"""Extract first sentence"""
s = re.split(r"\.\W", s.replace("\n", " ").strip())
s = s[0] if len(s) else ""
if not s.endswith("."):
s += "."
try:
return " ".join(s.split())
except AttributeError:
return s
class _DummyTerminal:
"""Used as a buffer to get prompt_toolkit bindings
"""
handle_return = None
input_transformer_manager = None
display_completions = None
editing_mode = "emacs"
auto_suggest = None
def bindings_from_prompt_toolkit(prompt_bindings: KeyBindingsBase) -> List[Binding]:
"""Collect bindings to a simple format that does not depend on prompt-toolkit internals"""
bindings: List[Binding] = []
for kb in prompt_bindings.bindings:
bindings.append(
Binding(
handler=Handler(
description=kb.handler.__doc__ or "",
identifier=create_identifier(kb.handler),
),
shortcut=Shortcut(
keys_sequence=[
str(k.value) if hasattr(k, "value") else k for k in kb.keys
],
filter=format_filter(kb.filter, skip={"has_focus_filter"}),
),
)
)
return bindings
INDISTINGUISHABLE_KEYS = {**KEY_ALIASES, **{v: k for k, v in KEY_ALIASES.items()}}
def format_prompt_keys(keys: str, add_alternatives=True) -> str:
"""Format prompt toolkit key with modifier into an RST representation."""
def to_rst(key):
escaped = key.replace("\\", "\\\\")
return f":kbd:`{escaped}`"
keys_to_press: List[str]
prefixes = {
"c-s-": [to_rst("ctrl"), to_rst("shift")],
"s-c-": [to_rst("ctrl"), to_rst("shift")],
"c-": [to_rst("ctrl")],
"s-": [to_rst("shift")],
}
for prefix, modifiers in prefixes.items():
if keys.startswith(prefix):
remainder = keys[len(prefix) :]
keys_to_press = [*modifiers, to_rst(remainder)]
break
else:
keys_to_press = [to_rst(keys)]
result = " + ".join(keys_to_press)
if keys in INDISTINGUISHABLE_KEYS and add_alternatives:
alternative = INDISTINGUISHABLE_KEYS[keys]
result = (
result
+ " (or "
+ format_prompt_keys(alternative, add_alternatives=False)
+ ")"
)
return result
if __name__ == '__main__':
here = Path(__file__).parent
dest = here / "source" / "config" / "shortcuts"
ipy_bindings = create_ipython_shortcuts(_DummyTerminal())
session = PromptSession(key_bindings=ipy_bindings)
prompt_bindings = session.app.key_bindings
assert prompt_bindings
# Ensure that we collected the default shortcuts
assert len(prompt_bindings.bindings) > len(ipy_bindings.bindings)
bindings = bindings_from_prompt_toolkit(prompt_bindings)
def sort_key(binding: Binding):
return binding.handler.identifier, binding.shortcut.filter
filters = []
with (dest / "table.tsv").open("w", encoding="utf-8") as csv:
for binding in sorted(bindings, key=sort_key):
sequence = ", ".join(
[format_prompt_keys(keys) for keys in binding.shortcut.keys_sequence]
)
if binding.shortcut.filter == "always":
condition_label = "-"
else:
# we cannot fit all the columns as the filters got too complex over time
condition_label = "ⓘ"
csv.write(
"\t".join(
[
sequence,
sentencize(binding.handler.description)
+ f" :raw-html:`<br>` `{binding.handler.identifier}`",
f':raw-html:`<span title="{html_escape(binding.shortcut.filter)}" style="cursor: help">{condition_label}</span>`',
]
)
+ "\n"
)