Repository: ipython/ipython

Issue:
Documenting keyboard shurtcuts
IMHO it would be sensible if we can provide description to newly introduced keyboard shortcuts.

Personally I hunted Google to learn about Ctrl+O, but there seem to be many other functions exposed with default shortcuts.

Ctrl+S / Ctrl+R =  `I-search` - seems to search some parts of history
Ctrl+Space = `Selection mode` - already discussed as feature from modern emacs bindings
...
Many other Ctrl+? bindings at least. 

So my suggestion is that we should provide reference to used bindings in documentation. At least just those exposed from PTK



Discussion:
That's extremely hard:

```
In [34]: len(get_ipython()._app.key_bindings_registry.key_bindings)
Out[34]: 491
```

The following can help getting started, but with all the filters, it's difficult to auto generate docs on when are some keybindings active. 

```
In [35]: for kb in get_ipython()._app.key_bindings_registry.key_bindings:
    ...:     print(kb.keys)
    ...:     doc = kb.handler.__doc__
    ...:     print(indent(dedent(doc if doc else '<NoDocs>'), '    '))
    ...:     print('\n')
```

Thanks, I would like to try and generate this, but I'm not familiar with PTK as you are ;)

For example, for many shortcuts I get just default doctring: https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/key_binding/bindings/basic.py#L108 i.e.:

``` python
In [3]: kb.keys
Out[3]: (Key('<C-D>'),)

In [4]: print(kb.handler.__doc__)

        First, for any of these keys, Don't do anything by default. Also don't
        catch them in the 'Any' handler which will insert them as data.

        If people want to insert these characters as a literal, they can always
        do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi
        mode.)
```

While I would like to get filter definition and this doctring for Ctrl+D: https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/key_binding/bindings/basic.py#L134

Any tips?

Hum, I'm not sure, I hacked that together yesterday...

It's there it just duplicated

```
from textwrap import indent, dedent

for i,kb in enumerate(get_ipython()._app.key_bindings_registry.key_bindings[:180]):
    doc = kb.handler.__doc__
    if not doc or doc.strip().startswith("First"):
        continue

    strkey = kb.keys[0] if isinstance(kb.keys[0], str) else kb.keys[0].name
    if 'C-D' not in strkey:
        continue
    print(i, strkey)


    print(indent(dedent(doc if doc else '<NoDocs>'), '    '))
    print('\n')
```

```
In [19]: run kb
73 <C-D>
    Delete text before cursor.


104 <C-D>
    Exit on Control-D when the input is empty.


133 <C-Delete>

    Delete word after cursor.
```

Thanks @Carreau :)

I further limited keys to `Ctrl+`, but results are not satisfactory for making it into docs, while I can't see other ways to get bindings:

```
<C-A> Start of line
<C-B> Character back
<C-C> Abort an incremental search and restore the original line
<C-C> Abort when Control-C has been pressed
<C-C> Cancel search
<C-C> Indent selected text
<C-C> Unindent selected text
<C-D> Delete text before cursor
<C-D> Exit on Control-D when the input is empty
<C-Delete> Delete word after cursor
<C-E> Accept suggestion
<C-E> Cancel completion
<C-E> End of line
<C-F> Accept suggestion
<C-F> Character forward
<C-G> Abort an incremental search and restore the original line
<C-G> Cancel selection
<C-G> Control + G: Cancel completion menu and validation state
<C-H> Backspace: delete before cursor
<C-H> Cancel search
<C-H> In navigation-mode, move cursor
<C-I> Ctrl-I is identical to "\t" Traditional tab-completion, where the first tab completes the common suffix and the second tab lists all the completions
<C-J> Apply the search
<C-J> Enter, accept input
<C-J> In navigation mode, pressing enter will always return the input
<C-J> Newline (in case of multiline input
<C-J> When enter pressed in isearch, quit isearch mode
<C-K> Go into digraph mode
<C-L> Clear whole screen and redraw
<C-Left> Cursor to start of previous word
<C-N> Arrow down and Control-N in navigation mode
<C-N> Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit
<C-N> Next line
<C-O> Insert newline, but don't move the cursor
<C-P> Arrow up and ControlP in navigation mode go up
<C-P> Control-P in vi edit mode on readline is history next, unlike default prompt toolkit
<C-P> Control-P: To previous completion
<C-P> Previous line
<C-Q> Quoted insert
<C-R> Vi-style backward search
<C-Right> Cursor to end of next word
<C-S> Vi-style forward search
<C-Space> Start of the selection (if the current buffer is not empty)
<C-SquareClose> When Ctl-] + a character is pressed
<C-T> Emulate Emacs transpose-char behavior: at the beginning of the buffer, do nothing
<C-U> Clears the line before the cursor position
<C-Underscore> Undo
<C-V> Enter block selection mode
<C-V> Exit block selection mode, or go from non block selection mode to block selection mode
<C-V> Insert a character literally (quoted insert)
<C-W> Cut selected text
<C-W> Delete the word before the cursor
<C-X> Complete file names
<C-X> Cut selected text
<C-X> Move cursor back and forth between the start and end of the current line
<C-X> Paste before cursor
<C-X> Pressing the ControlX - ControlL sequence in Vi mode does line completion based on the other lines in the document and the history
<C-Y> Accept current completion
<C-Y> Paste before cursor
<C-Z> By default, control-Z should literally insert Ctrl-Z
```

Yeah, we'll need to also sort by filter (here we're likely mixing up vi and emacs shortcut) plus a few things like whether the default buffer is focused. 

See `kb.filter`, `kb.filter.filters`. I **guess** once we have a reasonable solution we can try to push that on the prompt_toolkit side.

Ok, will look into filter

@Carreau I made my first step: https://gist.github.com/klonuo/534931b300744bed00caefeb2cd0615a

I added also `index.rst`, you can see it better in raw mode. In my local repo it is located in a new `shortcuts` subfolder relative to `source/config` folder and it is included in toc inside `source/config/index.rst` 

This is sample output: http://output.jsbin.com/nacizeg/1

Overall, shortcuts w/o documentation are skipped, and the rest are divided in 2 tables depending on filter - table for simple filers and one for conditional filters.

Waiting for suggestions.

Interesting to see what you are doing here.

So far, I haven't really thought about listing key bindings like this. So, in a way, it's experimental to use prompt-toolkit for doing this, but I'll keep this in mind.

By the way, if you don't like any of the docstrings, feel free to create pull requests. ;)

> Interesting to see what you are doing here.
> 
> So far, I haven't really thought about listing key bindings like this. So, in a way, it's experimental to use prompt-toolkit for doing this, but I'll keep this in mind.
> 
> By the way, if you don't like any of the docstrings, feel free to create pull requests. ;)

The other thing that seem to be requested is a way to customize shortcuts, I might have a shot at that at some point and likely have a shot at a declarative way that might be less powerful but allow some flexibility. 

BTW @klonuo I think that's really great ! Even if it's not perfect, I would be +1 in incorporating that in the documentation build-step, we can add on top that it is a limited list, that is autogenerated, but that might help People a lot. @willingc what do you think ? 

@klonuo Do you want to make a PR ?


Resolving patch:
diff --git a/docs/autogen_shortcuts.py b/docs/autogen_shortcuts.py
new file mode 100755
--- /dev/null
+++ b/docs/autogen_shortcuts.py
@@ -0,0 +1,83 @@
+from os.path import abspath, dirname, join
+
+from IPython.terminal.interactiveshell import KeyBindingManager
+
+
+def name(c):
+    s = c.__class__.__name__
+    return log_filters[s] if s in log_filters.keys() else s
+
+
+def sentencize(s):
+    """Extract first sentence
+    """
+    s = s.replace('\n', ' ').strip().split('.')
+    s = s[0] if len(s) else s
+    try:
+        return " ".join(s.split())
+    except AttributeError:
+        return s
+
+
+def most_common(lst, n=3):
+    """Most common elements occurring more then `n` times
+    """
+    from collections import Counter
+
+    c = Counter(lst)
+    return [k for (k, v) in c.items() if k and v > n]
+
+
+def multi_filter_str(flt):
+    """Yield readable conditional filter
+    """
+    assert hasattr(flt, 'filters'), 'Conditional filter required'
+
+    yield name(flt)
+    for subfilter in flt.filters:
+        yield name(subfilter)
+        if hasattr(subfilter, 'filter'):
+            yield name(subfilter.filter)
+
+
+log_filters = dict(_AndList='(And)', _OrList='(Or)', _Invert='(Inv)')
+
+kbm = KeyBindingManager.for_prompt()
+ipy_bindings = kbm.registry.key_bindings
+
+dummy_docs = []  # ignore bindings without proper documentation
+
+common_docs = most_common([kb.handler.__doc__ for kb in ipy_bindings])
+if common_docs:
+    dummy_docs.extend(common_docs)
+
+dummy_docs = list(set(dummy_docs))
+
+single_filter = dict()
+multi_filter = dict()
+for kb in ipy_bindings:
+    doc = kb.handler.__doc__
+    if not doc or doc in dummy_docs:
+        continue
+
+    shortcut = ' '.join([k if isinstance(k, str) else k.name for k in kb.keys])
+    shortcut += shortcut.endswith('\\') and '\\' or ''
+    if hasattr(kb.filter, 'filters'):
+        flt = ' '.join(multi_filter_str(kb.filter))
+        multi_filter[(shortcut, flt)] = sentencize(doc)
+    else:
+        single_filter[(shortcut, name(kb.filter))] = sentencize(doc)
+
+
+if __name__ == '__main__':
+
+    here = abspath(dirname(__file__))
+    dest = join(here, 'source', 'config', 'shortcuts')
+
+    with open(join(dest, 'single_filtered.csv'), 'w') as csv:
+        for k, v in sorted(single_filter.items()):
+            csv.write(':kbd:`{}`\t{}\t{}\n'.format(k[0], k[1], v))
+
+    with open(join(dest, 'multi_filtered.csv'), 'w') as csv:
+        for k, v in sorted(multi_filter.items()):
+            csv.write(':kbd:`{}`\t{}\t{}\n'.format(k[0], k[1], v))
diff --git a/docs/source/conf.py b/docs/source/conf.py
old mode 100644
new mode 100755


Test patch: