index
execution_count
cell_type
toolbar
started_at
completed_at
source
loc
metadata
outputs
1
unexecuted
In
[
]
markdown
# customizing the `IPython` contextual help
an original feature of `pidgy` is the use `jupyter` s contextual help to provide wysiwyg experience.
as author's write `pidgy` they reveal a preview of the output and explore variables in the workspace.
![image.png ](attachment:b0883578-72be-44f4-96e8-c170ead6f971.png "screen shot of a the modified contextual help for a cell in this notebook." )
metadata
7
an original feature of
pidgy
is the use
jupyter
s contextual help to provide wysiwyg experience.
as author's write
pidgy
they reveal a preview of the output and explore variables in the workspace.
2
unexecuted
In
[
]
markdown
* when source is found show help
* when inspection is found show the found results
* when is not found show the markdown preview
metadata
3
when source is found show help
when inspection is found show the found results
when is not found show the markdown preview
3
unexecuted
In
[
]
markdown
the inspector uses `jupyter` s rich display system.
we can send html and markdown to the contextual help.
metadata
2
the inspector uses
jupyter
s rich display system.
we can send html and markdown to the contextual help.
4
unexecuted
In
[
]
markdown
we create an ipython extension that modifies the `IPython` contextual help using rich displays.
we are going to create a wsyiwyg for
metadata
2
we create an ipython extension that modifies the
IPython
contextual help using rich displays.
we are going to create a wsyiwyg for
5
executed
In
[
1
]
code
def load_ipython_extension ( shell ):
__import__ ( "nest_asyncio" ) . apply () # just in case
shell . enable_html_pager = True
shell . kernel . do_inspect = types . MethodType ( do_inspect , shell )
metadata
4
0 outputs.
Out
[
1
]
6
executed
In
[
2
]
code
% reload_ext pidgy
import pandas
metadata
3
1 outputs.
Out
[
2
]
%reload_ext pidgy
import pandas
7
unexecuted
In
[
]
markdown
for any of this to work need to enable the html pager [`shell.enable_html_pager` ](https://ipython.readthedocs.io/en/stable/config/options/kernel.html#configtrait-ZMQInteractiveShell.enable_html_pager)
metadata
1
for any of this to work need to enable the html pager
shell.enable_html_pager
8
executed
In
[
3
]
code
shell . enable_html_pager = True
metadata
1
1 outputs.
Out
[
3
]
shell.enable_html_pager = True
9
unexecuted
In
[
]
markdown
`do_inspect` will be our new contextual help completer. it provides completion when:
* there is no content to inspect
* there was no result found
* you found [the bangs ](#the-bangs )
metadata
5
do_inspect
will be our new contextual help completer. it provides completion when:
there is no content to inspect
there was no result found
you found
the bangs
10
executed
In
[
4
]
code
def do_inspect ( self , cell , cursor_pos , detail_level = 1 , omit_sections = (), * , cache = {}):
post = post_bangs ( cell , get_bangs ( cell , cursor_pos ))
if post :
data = _do_inspect ( "" , 0 , detail_level = 0 , omit_sections = ())
data [ "data" ] = post
data [ "found" ] = True
else :
data = _do_inspect ( cell , cursor_pos , detail_level = 0 , omit_sections = ())
if not data [ "found" ]:
data . update ( inspect_weave ( cell , cursor_pos ))
return data
metadata
11
1 outputs.
Out
[
4
]
def do_inspect(self, cell, cursor_pos, detail_level=1, omit_sections=(), *, cache={}):
post = post_bangs(cell, get_bangs(cell, cursor_pos))
if post:
data = _do_inspect("", 0, detail_level=0, omit_sections=())
data["data"]= post
data["found"] = True
else:
data = _do_inspect(cell, cursor_pos, detail_level=0, omit_sections=())
if not data["found"]:
data.update(inspect_weave(cell, cursor_pos))
return data
11
executed
In
[
5
]
code
def inspect_weave ( code , cursor_pos , cache = {}):
data = dict ( found = True )
if not code . strip ():
data [ "data" ] = { "text/markdown" : help }
else :
tokens = cache . get ( code )
line , offset = lineno_at_cursor ( code , cursor_pos )
if tokens is None :
cache . clear ()
tokens = cache [ code ] = shell . tangle . parse ( code )
where = get_md_pointer ( tokens , line , offset )
data [ "data" ] = { "text/markdown" : F """` { "/" . join ( where ) } ` \n\n { code } """ }
return data
metadata
13
1 outputs.
Out
[
5
]
def inspect_weave(code, cursor_pos, cache={}):
data = dict(found=True)
if not code.strip():
data["data"] = {"text/markdown": help}
else:
tokens = cache.get(code)
line, offset= lineno_at_cursor(code, cursor_pos)
if tokens is None:
cache.clear()
tokens = cache[code] = shell.tangle.parse(code)
where = get_md_pointer(tokens, line, offset)
data["data"] = {"text/markdown": F"""`{"/".join(where)}`\n\n{code}"""}
return data
12
unexecuted
In
[
]
markdown
this is a hack! we'll hold the original inspect function and monkey patch it.
metadata
1
this is a hack! we'll hold the original inspect function and monkey patch it.
13
executed
In
[
6
]
code
locals () . setdefault ( "_do_inspect" , shell . kernel . do_inspect )
metadata
1
2 outputs.
Out
[
6
]
<bound method IPythonKernel.do_inspect of <ipykernel.ipkernel.IPythonKernel object at 0x7fd8eb2b0fa0>>
locals().setdefault("_do_inspect", shell.kernel.do_inspect)
14
executed
In
[
7
]
code
if I := ( "__file__" not in locals ()): load_ipython_extension ( shell )
metadata
1
1 outputs.
Out
[
7
]
if I := ("__file__" not in locals()): load_ipython_extension(shell)
15
unexecuted
In
[
]
markdown
### line positions and tokenization
it seemed important to have line and column numbers that align exactly with the code mirror line numbers and the line/column number in the bottom inspector ribbon. for extra confidence when writing pidgy we include the markdown block token the cursor is within.
metadata
3
it seemed important to have line and column numbers that align exactly with the code mirror line numbers and the line/column number in the bottom inspector ribbon. for extra confidence when writing pidgy we include the markdown block token the cursor is within.
16
executed
In
[
8
]
code
def lineno_at_cursor ( cell , cursor_pos = 0 ):
offset = 0
for i , line in enumerate ( cell . splitlines ( True )):
next_offset = offset + len ( line )
if not line . endswith ( ' \n ' ): next_offset += 1
if next_offset > cursor_pos : break
offset = next_offset
col = cursor_pos - offset
return i , col
def get_md_pointer ( tokens , line , offset ):
where = [ F "L { line + 1 } : { offset + 1 } " ]
for node in tokens :
if node . type == "root" : continue
if node . map :
if line < node . map [ 0 ]: break
elif node . map [ 0 ] <= line < node . map [ 1 ]: where . append ( node . type )
return where
metadata
18
1 outputs.
Out
[
8
]
def lineno_at_cursor(cell, cursor_pos=0):
offset = 0
for i, line in enumerate(cell.splitlines(True)):
next_offset = offset + len(line)
if not line.endswith('\n'): next_offset += 1
if next_offset > cursor_pos: break
offset = next_offset
col = cursor_pos-offset
return i, col
def get_md_pointer(tokens, line, offset):
where = [F"L{line+1}:{offset+1}"]
for node in tokens:
if node.type == "root": continue
if node.map:
if line < node.map[0]: break
elif node.map[0] <= line < node.map[1]: where.append(node.type)
return where
17
unexecuted
In
[
]
markdown
## the bangs
the bangs give extra interactivity. when inspecting code a group of exclamation `!!!` points with template the woven source, or with enough emphasism `!!!!!!` we'll actually run code. consider these easter eggs cause i want them.
metadata
3
the bangs give extra interactivity. when inspecting code a group of exclamation
!!!
points with template the woven source, or with enough emphasism
!!!!!!
we'll actually run code. consider these easter eggs cause i want them.
18
executed
In
[
9
]
code
def get_bangs ( cell , cursor_pos ):
if cell [ cursor_pos - 1 ] == "!" :
l , r = cell [: cursor_pos ], cell [ cursor_pos :]
return len ( l ) - len ( l . rstrip ( "!" )) + len ( r ) - len ( r . strip ( "!" ))
return 0
metadata
5
1 outputs.
Out
[
9
]
def get_bangs(cell, cursor_pos):
if cell[cursor_pos-1] == "!":
l, r = cell[:cursor_pos], cell[cursor_pos:]
return len(l) - len(l.rstrip("!")) + len(r) - len(r.strip("!"))
return 0
19
executed
In
[
10
]
code
def post_bangs ( cell , bangs ):
if bangs >= 6 :
result = shell . run_cell ( cell , store_history = False , silent = True )
error = result . error_before_exec or result . error_in_exec
if error : return { "text/markdown" : F """```pycon \n { "" . join (
traceback . format_exception ( type ( error ), error , error . __traceback__ )
) } \n ```""" }
else : shell . weave . update ()
if bangs >= 3 :
result = shell . environment . from_string ( cell , None , pidgy . environment . IPythonTemplate )
return { "text/markdown" : result . render ()}
metadata
11
1 outputs.
Out
[
10
]
def post_bangs(cell, bangs):
if bangs>=6:
result = shell.run_cell(cell, store_history=False, silent=True)
error = result.error_before_exec or result.error_in_exec
if error: return {"text/markdown": F"""```pycon\n{"".join(
traceback.format_exception(type(error), error, error.__traceback__)
)}\n```"""}
else: shell.weave.update()
if bangs >=3:
result = shell.environment.from_string(cell, None, pidgy.environment.IPythonTemplate)
return {"text/markdown": result.render()}
20
unexecuted
In
[
]
markdown
## the opportunity in the misses
the contextual help renders on keypress when a result is found.
metadata
3
the contextual help renders on keypress when a result is found.
21
executed
In
[
11
]
code
{ % set help_hits = inspection . found . mean () . round ( 2 ) * 100 % }
when we use the normal inspection . we find that there is a lot of time where the contextual help is not found .
for example , when we process all the code inputs in this document the inspector finds information {{ help_hits }} % of the time ,
meaning {{ 100 - help_hits }} % opportunity !!!
def measure ( x ): yield from ( shell . kernel . do_inspect ( x , i ) for i in range ( len ( x )))
shell . kernel . do_inspect = _do_inspect
inspection = pandas . Series ( In ) . apply ( measure ) . apply ( list ) . explode () . dropna () . apply ( pandas . Series )
load_ipython_extension ( shell )
metadata
10
1 outputs.
Out
[
11
]
when we use the normal inspection. we find that there is a lot of time where the contextual help is not found.
for example, when we process all the code inputs in this document the inspector finds information 36.0% of the time,
meaning 64.0% opportunity!!
def measure(x): yield from (shell.kernel.do_inspect(x, i) for i in range(len(x)))
shell.kernel.do_inspect = _do_inspect
inspection = pandas.Series(In).apply(measure).apply(list).explode().dropna().apply(pandas.Series)
load_ipython_extension(shell)
22
executed
In
[
12
]
code
a use is the blank screen is to provide a help interface for people to learn our new tool .
with links to good docs .
help = \
` pidgy ` is markdown / python literate programming language .
* indented code and fenced code are executed
## documentation
* jinja2 documentation
* markdown documentation
* python documentation
metadata
13
1 outputs.
Out
[
12
]
a use is the blank screen is to provide a help interface for people to learn our new tool.
with links to good docs.
help =\
pidgy
is markdown/python literate programming language.
indented code and fenced code are executed
jinja2 documentation
markdown documentation
python documentation
23
unexecuted
In
[
]
markdown
## including the current markdown token
sometimes when writing `pidgy` its possible to lose your you position. we include the line number and token the cursor is in
metadata
3
sometimes when writing
pidgy
its possible to lose your you position. we include the line number and token the cursor is in
24
unexecuted
In
[
]
markdown
an accidental outcome is this _hack_ adds contextual help to markdown cells. its good.
metadata
1
an accidental outcome is this
hack
adds contextual help to markdown cells. its good.