Not Docker

As of 10/24/2020, Docker cannot run “natively” in iSh. See Road to docker support, Issue #63.


Writing Markdown Using Jupyter

I like to do all of my writing on my iPad, and I prefer to write in Markdown.

I don’t use Markdown because of its simplicity. Rather, I use it because of its power. By writing in Markdown, I can use my text in different contexts – a blog, an e-book, a Word file, or a PDF.

In February 2021, I discovered that I can write Markdown in Jupyter. With a little tweaking, Carnets has become my go-to text editor. It is the most powerful Markdown editor I have used to date.

There are several benefits to doing so, which include:

  • Writing in cells.
  • Moving cells up or down.
  • Merging and splitting cells.
  • Having an outline for your headings.
  • Folding headings.
  • Having comments in each cell.
  • Having a live preview of the Markdown as you write. So, the Markdown is on the left and the preview is on the right.
  • Code syntax highlighting.
  • Folding code.
  • You can customize the way Jupyter looks by using a theme or adding custom CSS.
  • You can do so much from a keyboard because of built-in keyboard shortcuts. You can also modify the default shortcuts.

Folding Headings

Strength. A strength of writing Markdown in Jupyter is the ability to fold headings.

Downside. There is a downside to moving headings: A folded heading will not move all paragraphs below it! In Jupyter, each cell moves independently. Moving cells around when headings are folded causes confusion.

Workaround. There is a workaround: You can select several cells and move them together as a block. You still have to move the block one cell at a time, but at least you’re moving the entire chunk as opposed to individual pieces.

Tip. Fold the headings to help you look at the structure. When you move things around, make sure all headings are expanded, select several cells, and move them up or down once cell at a time.


You can enable the spellchecker nbextension that spell checks words as you type. It highlights words not in its dictionary. But it does not offer corrections.


Jupyter is flexible. You can tweak it. For me, I couldn’t use Jupyter until I increased the default font size. I also increased the width of the cells.

Writing a Book Using Jupyter

O’Reilly, Writing (and publishing) a Book Written in Jupyter Notebooks - Andreas Mueller (Columbia University), YouTube.

Customize Jupyter’s Look

Thee Ways to Change the CSS

You can change the default style of Jupyter by changing its CSS. There are several ways to do this.

  • Use a theme. See jupyterthemes.
  • You can create a custom.css file. The benefit of this method is that your changes happen across all Jupyter notebooks and you don’t have to worry about the syntax being exported.
  • You can use the magic HTML variable in a cell and run it: %%HTML. The benefit of this method is that you can define a different look in each notebook. The downside is that your code will be exported. But one workaround is to create a Markdown cell above your code with <!-- and a Markdown cell below your code with -->. With this workaround, your code will be hidden when you export to Markdown without having to take additional steps to edit the exported Markdown file.

These sites identify CSS that you can edit:

Change Default Font Size

Maybe the reason is that I’m getting older, but I found the default font size in Jupyter to be too small. There are several approaches that allow you to change the default font of a notebook.

In the custom.css file, you can add the following CSS:

  font-size: 18px;
  line-height: 26px;

In a Python cell, you add the following code and then run it:

<style type='text/css'>
font-size: 18px;
line-height: 26px; 

Change Width of Cells

To change the width of cells:

.container { width:100% !important; }

Another approach:

/* Make the notebook cells take almost all available width */
.container {
    width: 99% !important;

/* Prevent the edit cell highlight box from getting clipped;
 * important so that it also works when cell is in edit mode*/
div.cell.selected {
    border-left-width: 1px !important;

Use Jupyter to Blog to Jekyll

See Claire Duvallet, Blogging with jupyter notebooks and jekyll, March 7, 2018.

You can save the Jupyter files in Working Copy, but you need to exclude .ipynb files from Jekyll. To do that, adding the following to config.yml:

exclude:          ['*.ipynb']

N.B. Commit your work from Working Copy to GitHub often. One time, Working Copy removed the directory I was working in and I lost all of my work since my last commit to GitHub.

Hide or Remove Cell Content

*See Hide or remove content

Run Command Line Code

To execute terminal code from Jupyter, put an exclamation mark (!) in front of the code:

!pip install ipywidgets
!jupyter nbextensions enable --py widgetsnbextensions


# List Installed Extensions

!ls ~/Library/Jupyter/nbextensions/
# See a list of available jupyterthemes: 

!jt -l
## Select a theme:

!jt -t chesterish 
## Reset the theme to the default: 

!jt -r


!cp ~/Library/Jupyter/nbextensions/snippets/snippets.json ~/Documents/snippets.json
!cp ~/Documents/snippets.json ~/Library/Jupyter/nbextensions/snippets/snippets.json 


(Method 1) Modify a local dictionary and copy it to the default dictionary for spellchecker:

!cp ~/Documents/en_US_mod.dic ~/Library/Jupyter/nbextensions/spellchecker/typo/dictionaries/en_US_mod.dic 

(Method 2) Create and run a function to add a word and a custom dictionary.

To create the function:

import os.path
from jupyter_core.paths import jupyter_data_dir
from import ConfigManager

def AddToDictionary(word):

    local_base_url = os.path.join(
        'nbextensions', 'spellchecker', 'typo', 'dictionaries')

    lang_code = 'en_US'
    personal_dic = 'personal.dic'
    mod_dic = lang_code + '_mod' + '.dic'

    if not os.path.exists(local_base_url):
        print('creating directory {!r}'.format(local_base_url))

    #Add the word to the personal dictionary
    with open(local_base_url + '/' + personal_dic, 'a') as file:
        file.write(word + '\n')

    #Combine the personal and language dictionary
    with open(local_base_url + '/' + lang_code + ".dic", 'r') as file:
        old_lines =
    with open(local_base_url + '/' + personal_dic, 'r') as file:
        new_lines =
    with open(local_base_url + '/' + mod_dic, 'w') as file:

    #Make sure the spellchecker is using the modified dictionary
    cm = ConfigManager()
    rel_path = './typo/dictionaries/' + mod_dic
    cm.update('notebook', {'spellchecker': {'dic_url': rel_path}})

To run the function:


Word Count


import io
import os
from nbformat import current

total_markdown = 0
total_heading = 0
total_code = 0

for root, dirs, files in os.walk("."):
    for file in files:
        if file.endswith(".ipynb") and not file.endswith("checkpoint.ipynb") :
            #print(os.path.join(root, file))
            with, file), 'r', encoding='utf-8') as f:
                nb =, 'json')

            word_count_markdown = 0
            word_count_heading = 0
            word_count_code = 0
            for cell in nb.worksheets[0].cells:
                if cell.cell_type == "markdown":
                    word_count_markdown += len(cell['source'].replace('#', '').lstrip().split(' '))
                elif cell.cell_type == "heading":
                    word_count_heading += len(cell['source'].replace('#', '').lstrip().split(' '))
                elif cell.cell_type == "code":
                    word_count_code += len(cell['input'].replace('#', '').lstrip().split(' '))
            total_markdown += word_count_markdown
            total_heading += word_count_heading
            total_code += word_count_code

print("{} Words in notebooks' markdown" .format(total_markdown))
print("{} Words in notebooks' heading" .format(total_heading))
print("{} Words in notebooks' code" .format(total_code))

Work with Files in Carnets

List directories and files in the directory Carnets is currently pointed. For example, if you point Carnets to work in a Working Copy copy, this command lists what is in that directory.

404.html		_posts			p-te-buy-sell.ipynb
Gemfile			_sass			p-te-llc.ipynb
Gemfile.lock		p-te.ipynb		assets			p-tech.ipynb
Untitled.ipynb		feed.xml		p-writing.ipynb
_includes		pages
_layouts		p-browncroft.ipynb

List the items in the local Carnets folder on the iPad or iPhone. For example, at iPad > Carnets.

!ls ~/Documents/
cell_filter	notebook.json	snippets.json	toc2
en_US_mod.dic	snippets	spellchecker

List Carnets’ extensions:

ls ~/Library/Jupyter/nbextensions/
addbefore/			keyboard_shortcut_editor/
autosavetime/			latex_envs/
autoscroll/			limit_output/
cell_filter/			livemdpreview/
code_font_size/			load_tex_macros/
code_prettify/			move_selected_cells/
codefolding/			navigation-hotkeys/
codemirror_mode_extensions/	nbTranslate/
collapsible_headings/		notify/
comment-uncomment/		printview/
contrib_nbextensions_help_item/	python-markdown/
datestamper/			qtconsole/
equation-numbering/		rubberband/
execute_time/			ruler/
execution_dependencies/		runtools/
exercise/			scratchpad/
exercise2/			scroll_down/
export_embedded/		select_keymap/
freeze/				skill/
gist_it/			skip-traceback/
help_panel/			snippets/
hide_header/			snippets_menu/
hide_input/			spellchecker/
hide_input_all/			splitcell/
highlight_selected_word/	table_beautifier/
highlighter/			toc2/
hinterland/			toggle_all_line_numbers/
init_cell/			tree-filter/
ipysheet/			varInspector/
jupyter-js-widgets/		zenmode/

Get Data from Other Source



Shivam Dutt Sharma, [My GUI Programming Cheatsheet Python3 Jupyter](, Analytics Vidhya, Aug. 23, 2020.

Jupyter Documentation

Jupyter Project Documentation


Viewing Jupyter Notebooks

nbviewer: A simple way to share Jupyter Notebooks

binder: Turn a Git repo into a collection of interactive notebooks

Binder Examples on GitHub


Show where notebook extensions are installed:

pip show jupyter_contrib_nbextensions



Create Custom Themes