Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Moved scripts so they can be combined

AuthorDaveJarvis <email>
Date2020-07-14 17:48:35 GMT-0700
Commitd71c3b8c9f551f144948f8b06d23ffcfe86b8264
Parent943b78d
Delta747 lines added, 7 lines removed, 740-line increase
scripts/editor.sikuli/editor.py
app_bin = app_home + "/scrivenvar.jar"
-wpm_default_speed = 80
-wpm_typing_speed = wpm_default_speed
+wpm_typing_speed = 80
# -----------------------------------------------------------------------------
global wpm_typing_speed
wpm_typing_speed = wpm
-
-def restore_typing_speed():
- set_typing_speed( wpm_default_speed )
# -----------------------------------------------------------------------------
def recur( n, f, *args ):
for i in range( n ):
- f(*args)
+ f( *args )
random_wait()
# -----------------------------------------------------------------------------
# Emulate a typist who is typing in the given text.
# -----------------------------------------------------------------------------
def typer( text ):
- # ~25 is a reasonably realistic, fast typist.
for c in text:
type( c )
scripts/demo.sikuli/1594592396134.png
Binary files differ
scripts/demo.sikuli/1594593710440.png
Binary files differ
scripts/demo.sikuli/1594593794335.png
Binary files differ
scripts/demo.sikuli/1594594984108.png
Binary files differ
scripts/demo.sikuli/1594689573764.png
Binary files differ
scripts/demo.sikuli/demo.py
+# -----------------------------------------------------------------------------
+# Copyright 2020 White Magic Software, Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# Runs all scripts
+# -----------------------------------------------------------------------------
+
+import s01
+import s02
+import s03
+import s04
scripts/demo.sikuli/s01.py
+# -----------------------------------------------------------------------------
+# Copyright 2020 White Magic Software, Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# This script introduces the editor and its purpose.
+# -----------------------------------------------------------------------------
+from sikuli import *
+import sys
+
+if not "../editor.sikuli" in sys.path:
+ sys.path.append( "../editor.sikuli" )
+
+from editor import *
+
+# ---------------------------------------------------------------
+# Fresh start
+# ---------------------------------------------------------------
+rm( app_home + "/variables.yaml" )
+rm( app_home + "/untitled.md" )
+rm( dir_home + "/.scrivenvar" )
+
+# ---------------------------------------------------------------
+# Wait for application to launch
+# ---------------------------------------------------------------
+openApp( "java -jar " + app_bin )
+
+wait("1594187265140.png", 30)
+
+# Breathing room for video recording.
+wait( 4 )
+
+# ---------------------------------------------------------------
+# Introduction
+# ---------------------------------------------------------------
+set_typing_speed( 240 )
+
+heading( "What is this application?" )
+typer( "Well, this application is a text editor that supports interpolated definitions, ")
+typer( "a few different text formats, real-time preview, spell check ")
+typer( "as you tipe" )
+wait( 0.5 )
+recur( 3, backspace )
+typer( "ype, and R statements." )
+paragraph()
+wait( 1 )
+
+# ---------------------------------------------------------------
+# Definition demo
+# ---------------------------------------------------------------
+heading( "What are definitions?" )
+typer( "Watch. " )
+wait( .5 )
+
+# Focus the definition editor.
+click_create()
+recur( 4, tab )
+
+wait( .5 )
+rename_definition( "application" )
+
+insert()
+rename_definition( "title" )
+
+insert()
+rename_definition( "Scrivenvar" )
+
+# Set focus to the text editor.
+tab()
+
+typer( "The left-hand pane contains a nested, folder-like structure of names " )
+typer( "and values that are called *definitions*. " )
+wait( .5 )
+typer( "Such definitions can simplify updating documents. " )
+wait( 1 )
+
+edit_find( "this application" )
+typer( "$application.title$" )
+
+edit_find_next()
+typer( "$application.title$" )
+
+type( Key.END, Key.CTRL )
+
+typer( "The right-hand pane shows the result after having substituted definition " )
+typer( "values into the document." )
+
+paragraph()
+typer( "Now nobody wants to type definition names all the time. Instead, type any " )
+typer( "partial definition value followed by `Ctrl+Space`, such as: scr" )
+wait( 0.5 )
+autoinsert()
+wait( 1 )
+typer( ". *Much* better!" )
+paragraph()
+
+heading( "What is interpolation?" )
+typer( "Definition values can reference definition names. " )
+wait( .5 )
+typer( "The definition names act as placeholders. Substituting placeholders with " )
+typer( "their definition value is called *interpolation*. Let's see how it works." )
+wait( 2 )
scripts/demo.sikuli/s02.py
+# -----------------------------------------------------------------------------
+# Copyright 2020 White Magic Software, Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# This script demonstrates how to use interpolated strings.
+# -----------------------------------------------------------------------------
+from sikuli import *
+import sys
+
+if not "../editor.sikuli" in sys.path:
+ sys.path.append( "../editor.sikuli" )
+
+from editor import *
+
+# -----------------------------------------------------------------------------
+# Open sample chapter.
+# -----------------------------------------------------------------------------
+file_open()
+type( Key.UP, Key.ALT )
+wait( 1 )
+typer( Key.END )
+wait( 1 )
+enter()
+wait( 0.5 )
+enter()
+wait( 1 )
+
+# -----------------------------------------------------------------------------
+# Open the corresponding definition file.
+# -----------------------------------------------------------------------------
+file_open()
+recur( 2, down )
+wait( 1 )
+enter()
+wait( 1 )
+
+# -----------------------------------------------------------------------------
+# Edit the sample document.
+# -----------------------------------------------------------------------------
+set_typing_speed( 80 )
+
+type( Key.HOME, Key.CTRL )
+recur( 2, down )
+
+# Grey
+recur( 3, skip_right )
+autoinsert()
+
+# 34
+recur( 4, skip_right )
+autoinsert()
+
+# Central
+recur( 10, skip_right )
+autoinsert()
+
+# London
+skip_right()
+autoinsert()
+
+# Hatchery
+skip_right()
+autoinsert()
+
+# and Conditioning
+recur( 2, select_word_right )
+delete()
+
+# Centre
+skip_right()
+autoinsert()
+
+set_typing_speed( 220 )
+
+typer( " Let's interpolate those four definitions instead!" )
+wait( 4 )
+recur( 13, type, Key.BACKSPACE, Key.CTRL )
+recur( 9, backspace )
+
+set_typing_speed( 60 )
+
+typer( "name$" )
+wait( 2 )
+
+# Collapse all definitions
+tab()
+recur( 8, typer, Key.LEFT )
+
+# Expand to city
+recur( 4, typer, Key.RIGHT )
+
+# Jump to name
+recur( 2, down )
+recur( 2, typer, Key.RIGHT )
+
+# Open the text field to show the full value
+typer( Key.F2 )
+
+# Traverse the text field
+home()
+recur( 16, type, Key.RIGHT, Key.CTRL )
+esc()
+
+restore_typing_speed()
+
+tab()
+type( Key.HOME, Key.CTRL )
+edit_find( "Director" )
+autoinsert()
+
+edit_find_next()
+autoinsert()
+
+edit_find_next()
+typer( Key.RIGHT )
+recur( 2, delete )
+autoinsert()
+typer( "'s" )
+
+edit_find( "Hatcheries" )
+autoinsert()
+
+# and Conditioning
+recur( 2, select_word_right )
+delete()
+
+edit_find( "Central" )
+autoinsert()
+
+skip_right()
+autoinsert()
+
+typer( " How about a different city?" )
+wait( 2 )
+recur( 5, type, Key.BACKSPACE, Key.CTRL )
+wait( 1 )
+tab()
+typer( Key.F2 )
+typer( "Seattle" )
+enter()
+tab()
+wait( 2 )
+
+type( Key.END, Key.CTRL )
+paragraph()
+typer( "No?" )
+paragraph()
+
+tab()
+typer( Key.F2 )
+typer( "London" )
+enter()
+
+tab()
+typer( "Organizing definitions is left to your ")
+typer( "doub" )
+autoinsert()
+typer( " Good imagination." )
+tab()
+
+# Jump to "char" definition
+home()
+
+# Jump to "char.a.primary.name" definition
+recur( 6, typer, Key.RIGHT )
+
+# Jump to "char.a.primary.caste" definition
+down()
+typer( Key.RIGHT )
+
+# Jump to root-level "caste" definition
+recur( 7, down )
+
+# Reselect "super"
+recur( 5, typer, Key.RIGHT )
+wait( 2 )
+
+# Close the window, no save
+type( "w", Key.CTRL )
+wait( 0.5 )
+tab()
+wait( 0.5 )
+typer( Key.SPACE )
+wait( 1 )
scripts/demo.sikuli/s03.py
+# -----------------------------------------------------------------------------
+# Copyright 2020 White Magic Software, Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# This script introduces images and R.
+# -----------------------------------------------------------------------------
+from sikuli import *
+import sys
+
+if not "../editor.sikuli" in sys.path:
+ sys.path.append( "../editor.sikuli" )
+
+from editor import *
+
+set_typing_speed( 80 )
+
+file_open()
+type( Key.UP, Key.ALT )
+wait( 0.5 )
+home()
+wait( 0.25 )
+enter()
+wait( 1 )
+end()
+wait( 0.25 )
+enter()
+wait( 1 )
+
+set_typing_speed( 200 )
+
+paragraph()
+heading( "What text formats are supported?" )
+
+typer( "Scr" )
+autoinsert()
+typer( " supports Markdown, R Markdown, XML, and R XML; however, the software " )
+typer( "architecture enables it to easily add new formats. The following figure " )
+typer( "depicts the overall architecture: " )
+paragraph()
+typer( "![](../writing/images/architecture)" )
+paragraph()
+typer( "Many text editors can only open one type of plain text markup format that is " )
+typer( "only output as HTML. With a little more effort, text editors could support " )
+typer( "multiple input and output formats. Scr" )
+autoinsert()
+typer( " does so and goes one step further by introducing interpolated definitions." )
+paragraph()
+typer( "Kitten interlude:" )
+paragraph()
+typer( "![](https://i.imgur.com/jboueQH.jpg)" )
+paragraph()
+
+heading( "What is R?" )
+typer( "R is a programming language. You might have noticed a few potential grammar " )
+typer( "problems with direct substitution. Rules for possessive forms, numbers, and " )
+typer( "other quirks can be tackled using R." )
+
+# -----------------------------------------------------------------------------
+# Demo bootstrapping
+# -----------------------------------------------------------------------------
+
+# Jump to the end
+type( Key.END, Key.CTRL )
+paragraph()
+
+set_typing_speed( 300 )
+heading( "How is R used?" )
+typer( "R must be instructed where to find script files and what ones to load. The " )
+typer( "*working directory* is the full path to those R files; the *startup script* " )
+typer( "defines what R files to load. Both preferences must be changed before prose " )
+typer( "may be processed. Preferences can be opened using either the " )
+typeln( "**Edit > Preferences** menu or by pressing `Ctrl+Alt+s`. Here goes!" )
+wait( 2 )
+
+# -----------------------------------------------------------------------------
+# Select the R script directory
+# -----------------------------------------------------------------------------
+
+# Change the working directory by clicking "Browse"
+type( "s", Key.CTRL + Key.ALT )
+wait("1594592396134.png", 1)
+click("1594592396134.png")
+wait( 0.5 )
+
+# Navigate to and select the "r" directory
+type( Key.UP, Key.ALT )
+wait( 0.5 )
+end()
+wait( 0.5 )
+enter()
+wait( 0.5 )
+end()
+wait( 0.5 )
+type( Key.UP )
+wait( 0.5 )
+recur( 2, tab )
+wait( 0.5 )
+enter()
+wait( 1 )
+
+# -----------------------------------------------------------------------------
+# Set the R startup script instructions
+# -----------------------------------------------------------------------------
+
+wait("1594593710440.png", 5)
+click("1594593710440.png")
+
+set_typing_speed( 440 )
+
+typeln( "setwd( '$application.r.working.directory$' )" )
+typeln( "assign( 'anchor', '$date.anchor$', envir = .GlobalEnv )" )
+typeln( "source( 'pluralize.R' )" )
+typeln( "source( 'possessive.R' )" )
+typeln( "source( 'conversion.R' )" )
+typeln( "source( 'csv.R' )" )
+
+wait("1594593794335.png", 3)
+click("1594593794335.png")
+
+paragraph()
+set_typing_speed( 220 )
+
+typer( "R is now configured. The startup script and other R " )
+typer( "files can be found in the " )
+typer( "[repository](https://github.com/DaveJarvis/scrivenvar/tree/master/R). " )
+wait( 1.5 )
+
+# Wait for the browser to appear.
+wait("1594594984108.png", 5)
+click("1594594984108.png")
+
+wait( 5 )
+click("1594689573764.png")
+
+paragraph()
+typer( "Next, we'll see how definitions and R can work together." )
+wait( 2 )
scripts/demo.sikuli/s04.py
+# -----------------------------------------------------------------------------
+# Copyright 2020 White Magic Software, Ltd.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# This script demonstrates using R.
+# -----------------------------------------------------------------------------
+from sikuli import *
+import sys
+
+if not "../editor.sikuli" in sys.path:
+ sys.path.append( "../editor.sikuli" )
+
+from editor import *
+
+set_typing_speed( 220 )
+
+# -----------------------------------------------------------------------------
+# Open the demo text.
+# -----------------------------------------------------------------------------
+file_open()
+type( Key.UP, Key.ALT )
+wait( 0.5 )
+end()
+wait( 0.5 )
+enter()
+wait( 0.5 )
+down()
+wait( 0.5 )
+enter()
+wait( 2 )
+
+# -----------------------------------------------------------------------------
+# Re-open the corresponding definition file.
+# -----------------------------------------------------------------------------
+file_open()
+recur( 2, down )
+wait( 1 )
+enter()
+wait( 2 )
+
+# -----------------------------------------------------------------------------
+# Brief introduction to R
+# -----------------------------------------------------------------------------
+type( Key.HOME, Key.CTRL )
+end()
+paragraph()
+
+typer( "## Using R" )
+paragraph()
+typer( "Insert R code into documents as follows: `r# 1+1`. " )
+wait( 1.5 )
+typer( "Notice how the right-hand pane shows the computed result. I'll wait. " )
+wait( 3 )
+typer( "The syntax is: open backtick, r#, *computable expression*, close " )
+typer( "backtick. That expression can be any valid R statement. The status bar " )
+typer( "will provide clues when an R expression cannot be computed by the " )
+typer( "editor. `r# glitch`" )
+wait( 4 )
+recur( 11, backspace )
+typer( "Let's swap 34 storeys for a definition value and replace the number " )
+typer( "according to the Chicago Manual of Style (cms) rules." )
+
+# -----------------------------------------------------------------------------
+# Demo pluralization
+# -----------------------------------------------------------------------------
+set_typing_speed( 80 )
+
+edit_find( "34" )
+autoinsert()
+
+edit_find( "x(" )
+typer( "cms(" )
+
+edit_find( "storeys." )
+typer( "34." )
+autoinsert()
+edit_find( "x(" )
+typer( "pl( 'storey'," )
+wait( 4 )
+
+tab()
+rename_definition( "1" )
+wait( 4 )
+rename_definition( "142" )
+wait( 4 )
+rename_definition( "34" )
+wait( 4 )
+tab()
+
+# -----------------------------------------------------------------------------
+# Demo possessives (it, her, his, Director)
+# -----------------------------------------------------------------------------
+type( Key.HOME, Key.CTRL )
+edit_find( "Director" )
+autoinsert()
+edit_find_next()
+autoinsert()
+edit_find_next()
+autoinsert()
+type( Key.RIGHT )
+recur( 2, delete )
+autoinsert()
+home()
+edit_find( "x(" )
+typer( "pos(" )
+wait( 2 )
+
+tab()
+rename_definition( "Headmistress" )
+wait( 4 )
+rename_definition( "Director" )
+wait( 2 )
+tab()
+
+type( Key.END, Key.CTRL )
+paragraph()
+typer( "Other possessives: `r# pos( 'it' )`, `r# pos( 'her' )`, `r# pos( 'his' )`, " )
+typer( "and `r# pos( 'my' )`." )
+
+# -----------------------------------------------------------------------------
+# Demo conversion, including ordinal numbers
+# -----------------------------------------------------------------------------
+set_typing_speed( 160 )
+
+paragraph()
+heading( "Date Conversions" )
+typer( "Mixing R code with definitions invites endless possibilities. " )
+typer( "Imagine someone racing to the " )
+typer( "`r#cms( v$location$breeder$storeys, ordinal=TRUE )` floor, whereby that " )
+typer( "ordinal stems from the Hatchery's storeys' definition. Or how about " )
+typer( "a complex timeline where dates are expressed in days relative to one " )
+typer( "point in time. Let's call this the *anchor date* and define it." )
+
+tab()
+home()
+typer( Key.SPACE )
+insert()
+rename_definition( "date" )
+insert()
+rename_definition( "anchor" )
+insert()
+rename_definition( "1969-10-29" )
+tab()
+
+paragraph()
+typer( "Next, set an R variable named `now` to the current date" )
+typer( "`r# now = format( Sys.time(), '%Y-%m-%d' ); ''`--- the empty single quotes " )
+typer( "prevent the date from appearing in the output document. " )
+
+paragraph()
+typer( "We set the anchor date to `r# annal()`, which was " )
+typer( "`r# elapsed( 0, days( v$date$anchor, format( Sys.time(), '%Y-%m-%d' ) ) )` " )
+typer( "ago from `r# format( as.Date( now ), '%B %d, %Y' )`. " )
+
+# -----------------------------------------------------------------------------
+# Demo CSV file import
+# -----------------------------------------------------------------------------
+paragraph()
+heading( "Tabular Data" )
+typer( "The following table shows average Canadian lifespans by birth " )
+typer( "year and sex:" )
+paragraph()
+typer( "`r# csv2md( '../data.csv', total=FALSE )`" )
+paragraph()
+typer( "Calling `csv2md` converts the comma-separated values in the spreadsheet " )
+typer( "to a table formatted using Markdown. The HTML preview pane changes the " )
+typer( "appearance of the resulting table. Using `../data.csv` instructs R to " )
+typer( "open `data.csv` from one directory above the *working directory*." )
+
+# -----------------------------------------------------------------------------
+# Demo HTML export
+# -----------------------------------------------------------------------------
+paragraph()
+heading( "Export" )
+typer( "Retrieve the output HTML by using the **Edit > Copy HTML** menu. Let's " )
+typer( "peek at the output." )
+wait( 2 )
+
+type( "e", Key.ALT )
+wait( 0.5 )
+down()
+wait( 0.25 )
+enter()
+wait( 0.25 )
+
+type( "a", Key.CTRL )
+wait( 0.25 )
+type( "v", Key.CTRL )
+wait( 5 )
+
+set_typing_speed( 40 )
+
+# Jump to page bottom (should already be there, but just in case)
+type( Key.END, Key.CTRL )
+recur( 3, typer, Key.PAGE_UP )
+type( Key.HOME, Key.CTRL )
+wait( 3 )
+
+set_typing_speed( 220 )
+type( "z", Key.CTRL )
+type( Key.END, Key.CTRL )
+
+paragraph()
+typer( "That's all for now, thank you!" )
+wait( 5 )
+
+# Delete the anchor date.
+tab()
+end()
+recur( 2, type, Key.UP )
+delete()
+tab()