Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
M .idea/compiler.xml
22
<project version="4">
33
  <component name="CompilerConfiguration">
4
    <bytecodeTargetLevel target="11" />
4
    <bytecodeTargetLevel target="14" />
55
  </component>
66
</project>
M .idea/misc.xml
22
<project version="4">
33
  <component name="ExternalStorageConfigurationManager" enabled="true" />
4
  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="14" project-jdk-type="JavaSDK">
4
  <component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="true" project-jdk-name="14" project-jdk-type="JavaSDK">
55
    <output url="file://$PROJECT_DIR$/build" />
66
  </component>
M R/conversion.R
202202
# Returns a human-readable string that provides the elapsed time between
203203
# two numbers in terms of years, months, and days. If any unit value is zero,
204
# the unit is not included. The words (year, month, day) are pluralised
204
# the unit is not included. The words (year, month, day) are pluralized
205205
# according to English grammar. The numbers are written out according to
206206
# Chicago Manual of Style. This applies the serial comma.
...
288288
# -----------------------------------------------------------------------------
289289
pl.numeric <- function( s, n ) {
290
  concat( cms( n ), concat( " ", pluralise( s, n ) ) )
290
  concat( cms( n ), concat( " ", pluralize( s, n ) ) )
291291
}
292292
293293
# -----------------------------------------------------------------------------
294
# Pluralise s if n is not equal to 1.
294
# Pluralize s if n is not equal to 1.
295295
# -----------------------------------------------------------------------------
296296
pl <- function( s, n=2 ) {
M build.gradle
3333
  // JavaFX
3434
  implementation 'org.reactfx:reactfx:1.4.1'
35
  implementation 'org.controlsfx:controlsfx:11.0.1'
35
  implementation 'org.controlsfx:controlsfx:11.0.2'
3636
  implementation 'org.fxmisc.richtext:richtextfx:0.10.5'
3737
  implementation 'org.fxmisc.wellbehaved:wellbehavedfx:0.3.3'
...
132132
133133
version = gitVersion()
134
sourceCompatibility = JavaVersion.VERSION_11
135134
136135
def launcherClassName = "com.${applicationName}.Launcher"
M scripts/01.sikuli/01.py
3737
rm( app_home + "/variables.yaml" )
3838
rm( app_home + "/untitled.md" )
39
rm( home + "/.scrivenvar" )
39
rm( dir_home + "/.scrivenvar" )
4040
4141
# ---------------------------------------------------------------
...
5050
set_typing_speed( 240 )
5151
52
header( "What is this application?" )
52
heading( "What is this application?" )
5353
typer( "Well, this application is a text editor that supports interpolated definitions, ")
5454
typer( "a few different text formats, real-time preview, spell check ") 
...
6363
# Definition demo
6464
# ---------------------------------------------------------------
65
header( "What are definitions?" )
65
heading( "What are definitions?" )
6666
typer( "Watch. " )
6767
wait( .5 )
...
102102
paragraph()
103103
104
header( "What is interpolation?" )
104
heading( "What is interpolation?" )
105105
typer( "Definition values can reference definition names. " )
106106
wait( .5 )
107107
typer( "The definition names act as placeholders. Substituting placeholders with " )
108
typer( "their defined value is called *interpolation*. Let's see how it works." )
108
typer( "their definition value is called *interpolation*. Let's see how it works." )
109109
wait( 2 )
110110
M scripts/03.sikuli/03.py
3333
3434
set_typing_speed( 80 )
35
3536
file_open()
3637
type( Key.UP, Key.ALT )
...
4849
4950
paragraph()
50
header( "What text formats are supported?" )
51
heading( "What text formats are supported?" )
5152
5253
typer( "Scr" )
5354
autoinsert()
5455
typer( " supports Markdown, R Markdown, XML, and R XML; however, the software " )
5556
typer( "architecture enables it to easily add new formats. The following figure " )
5657
typer( "depicts the overall architecture: " )
5758
paragraph()
5859
typer( "![](../writing/images/architecture)" )
5960
paragraph()
60
typer( "Most text editors read a single format and convert it to one other format. " )
61
typer( "With a little more effort, text editors can support many input and output " )
62
typer( "formats. Scr" )
61
typer( "Many text editors can only open one type of plain text markup format that is " )
62
typer( "only output as HTML. With a little more effort, text editors could support " )
63
typer( "multiple input and output formats. Scr" )
6364
autoinsert()
64
typer( " goes one step further by introducing interpolated definitions." )
65
typer( " does so and goes one step further by introducing interpolated definitions." )
6566
paragraph()
6667
typer( "Kitten interlude:" )
6768
paragraph()
6869
typer( "![](https://i.imgur.com/jboueQH.jpg)" )
6970
paragraph()
7071
71
header( "What is R?" )
72
heading( "What is R?" )
7273
typer( "R is a programming language. You might have noticed a few potential grammar " )
7374
typer( "problems with direct substitution. Rules for possessive forms, numbers, and " )
74
typer( "other linguistic exceptions can be addressed using R. Let's take a look!" )
75
typer( "other quirks can be tackled using R." )
76
77
# -----------------------------------------------------------------------------
78
# Demo bootstrapping
79
# -----------------------------------------------------------------------------
80
81
# Jump to the end
82
type( Key.END, Key.CTRL )
83
paragraph()
84
85
set_typing_speed( 300 )
86
heading( "How is R used?" )
87
typer( "R must be instructed where to find script files and what ones to load. The " )
88
typer( "*working directory* is the full path to those R files; the *startup script* " )
89
typer( "defines what R files to load. Both preferences must be changed before prose " )
90
typer( "may be processed. Preferences can be opened using either the " )
91
typeln( "**Edit > Preferences** menu or by pressing `Ctrl+Alt+s`. Here goes!" ) 
92
wait( 5 )
93
94
# -----------------------------------------------------------------------------
95
# Select the R script directory
96
# -----------------------------------------------------------------------------
97
98
# Change the working directory by clicking "Browse"
99
type( "s", Key.CTRL + Key.ALT )
100
wait("1594592396134.png", 1)
101
click("1594592396134.png")
102
wait( 0.5 )
103
104
# Navigate to and select the "r" directory
105
type( Key.UP, Key.ALT )
106
wait( 0.5 )
107
end()
108
wait( 0.5 )
109
enter()
110
wait( 0.5 )
111
end()
112
wait( 0.5 )
113
type( Key.UP )
114
wait( 0.5 )
115
recur( 2, tab )
116
wait( 0.5 )
117
enter()
118
wait( 1 )
119
120
# -----------------------------------------------------------------------------
121
# Set the R startup script instructions
122
# -----------------------------------------------------------------------------
123
124
wait("1594593710440.png", 5)
125
click("1594593710440.png")
126
127
set_typing_speed( 440 )
128
129
typeln( "setwd( '$application.r.working.directory$' )" )
130
typeln( "assign( ""anchor"", '$date.anchor$', envir = .GlobalEnv )" )
131
typeln( "source( 'pluralize.R' )" )
132
typeln( "source( 'possessive.R' )" )
133
typeln( "source( 'conversion.R' )" )
134
typeln( "source( 'csv.R' )" )
135
wait( 2 )
136
137
wait("1594593794335.png", 5)
138
click("1594593794335.png")
139
140
paragraph()
141
set_typing_speed( 220 )
142
143
typer( "R is now configured. The startup script and other R " )
144
typer( "files can be found in the " )
145
typer( "[repository](https://github.com/DaveJarvis/scrivenvar/tree/master/R). " )
146
wait( 2.25 )
147
148
wait("1594594984108.png", 5)
149
click("1594594984108.png")
150
151
wait( 5 )
152
click("1594595370191.png")
153
154
paragraph()
155
typer( "Next, we'll see how definitions and R can work together." )
156
wait( 2 )
75157
A scripts/03.sikuli/1594592396134.png
Binary file
A scripts/03.sikuli/1594593710440.png
Binary file
A scripts/03.sikuli/1594593794335.png
Binary file
A scripts/03.sikuli/1594594984108.png
Binary file
A scripts/03.sikuli/1594595370191.png
Binary file
M scripts/04.sikuli/04.py
3232
from editor import *
3333
34
set_typing_speed( 220 )
35
3436
# -----------------------------------------------------------------------------
3537
# Open the demo text.
...
4850
4951
# -----------------------------------------------------------------------------
50
# Demo bootstrapping
52
# Re-open the corresponding definition file.
5153
# -----------------------------------------------------------------------------
54
file_open()
55
recur( 2, down )
56
wait( 1 )
57
enter()
58
wait( 1 )
5259
53
# Jump to the end.
54
type( Key.END, Key.CTRL )
60
# -----------------------------------------------------------------------------
61
# Brief introduction to R
62
# -----------------------------------------------------------------------------
63
type( Key.HOME, Key.CTRL )
64
end()
5565
paragraph()
56
57
type( "s", Key.CTRL + Key.ALT )
58
wait( 0.25 )
5966
67
typer( "## Using R" )
68
paragraph()
69
typer( "Insert R code into documents as follows: `r# 1+1`. " )
70
wait( 1.5 )
71
typer( "Notice how the right-hand pane shows the computed result. I'll wait. " )
72
wait( 3 )
73
typer( "The syntax is: open backtick, r#, *computable expression*, close " )
74
typer( "backtick. That expression can be any valid R statement. The status bar " ) 
75
typer( "will provide clues when an R expression cannot be computed by the " )
76
typer( "editor. `r# glitch`" )
77
wait( 4 )
78
recur( 11, backspace )
79
typer( "Let's swap 34 storeys for a definition value and replace the number " )
80
typer( "according to the Chicago Manual of Style (cms) rules." )
6081
6182
# -----------------------------------------------------------------------------
6283
# Demo pluralization
6384
# -----------------------------------------------------------------------------
85
set_typing_speed( 80 )
86
87
edit_find( "34" )
88
autoinsert()
89
90
edit_find( "x(" )
91
typer( "cms(" )
92
93
edit_find( "storeys." )
94
typer( "34." )
95
autoinsert()
96
edit_find( "x(" )
97
typer( "pl( 'storey'," )
98
wait( 4 )
6499
100
tab()
101
rename_definition( "1" )
102
wait( 4 )
103
rename_definition( "142" )
104
wait( 4 )
105
rename_definition( "34" )
106
wait( 4 )
107
tab()
65108
66109
# -----------------------------------------------------------------------------
67
# Demo possessives (it, she, he, Ross)
110
# Demo possessives (it, her, his, Director)
68111
# -----------------------------------------------------------------------------
112
type( Key.HOME, Key.CTRL )
113
edit_find( "Director" )
114
autoinsert()
115
edit_find_next()
116
autoinsert()
117
edit_find_next()
118
autoinsert()
119
type( Key.RIGHT )
120
recur( 2, delete )
121
autoinsert()
122
home()
123
edit_find( "x(" )
124
typer( "pos(" )
125
wait( 2 )
126
127
tab()
128
rename_definition( "Headmistress" )
129
wait( 4 )
130
rename_definition( "Director" )
131
wait( 2 )
132
tab()
69133
134
type( Key.END, Key.CTRL )
135
paragraph()
136
typer( "Other possessives: `r# pos( 'it' )`, `r# pos( 'her' )`, `r# pos( 'his' )`, " )
137
typer( "and `r# pos( 'my' )`." )
70138
71139
# -----------------------------------------------------------------------------
72140
# Demo conversion, including ordinal numbers
73141
# -----------------------------------------------------------------------------
142
set_typing_speed( 160 )
74143
75
# -----------------------------------------------------------------------------
76
# Demo Chicago Manual of Style
77
# -----------------------------------------------------------------------------
144
paragraph()
145
heading( "Date Conversions" )
146
typer( "Mixing R code with definitions invites endless possibilities. " )
147
typer( "Imagine someone racing to the " ) 
148
typer( "`r#cms( v$location$breeder$storeys, ordinal=TRUE )` floor, whereby that " )
149
typer( "ordinal stems from the Hatchery's storeys' definition. Or how about " )
150
typer( "a complex timeline where dates are expressed in days relative to one " )
151
typer( "point in time. Let's call this the *anchor date* and define it." )
152
153
tab()
154
home()
155
typer( Key.SPACE )
156
insert()
157
rename_definition( "date" )
158
insert()
159
rename_definition( "anchor" )
160
insert()
161
rename_definition( "1969-10-29" )
162
tab()
163
164
paragraph()
165
typer( "Next, set an R variable named `now` to the current date" )
166
typer( "`r# now = format( Sys.time(), '%Y-%m-%d' ); ''`--- the empty single quotes " )
167
typer( "prevent the date from appearing in the output document. " )
78168
169
paragraph()
170
typer( "We set the anchor date to `r# annal()`, which was " )
171
typer( "`r# elapsed( 0, days( v$date$anchor, format( Sys.time(), '%Y-%m-%d' ) ) )` " )
172
typer( "ago from `r# format( as.Date( now ), '%B %d, %Y' )`. " )
79173
80174
# -----------------------------------------------------------------------------
81175
# Demo CSV file import
176
# -----------------------------------------------------------------------------
177
paragraph()
178
heading( "Tabular Data" )
179
typer( "The following table shows average Canadian lifespans by birth " )
180
typer( "year and sex:" )
181
paragraph()
182
typer( "`r# csv2md( '../data.csv', total=FALSE )`" )
183
paragraph()
184
typer( "Calling `csv2md` converts the comma-separated values in the spreadsheet " )
185
typer( "to a table formatted using Markdown. The HTML preview pane changes the " )
186
typer( "appearance of the resulting table. Using `../data.csv` instructs R to " )
187
typer( "open `data.csv` from one directory above the *working directory*." )
188
189
# -----------------------------------------------------------------------------
190
# Demo HTML export
82191
# -----------------------------------------------------------------------------
192
paragraph()
193
heading( "Export" )
194
typer( "Retrieve the output HTML by using the **Edit > Copy HTML** menu. Let's " )
195
typer( "look at the output document." )
83196
197
type( "e", Key.ALT )
198
wait( 0.5 )
199
down()
200
wait( 0.25 )
201
enter()
202
wait( 0.25 )
84203
204
type( "a", Key.CTRL )
205
wait( 0.25 )
206
type( "v", Key.CTRL )
85207
M scripts/editor.sikuli/editor.py
3333
from os.path import expanduser
3434
35
home = expanduser( "~" )
36
app_home = home + "/bin"
35
dir_home = expanduser( "~" )
36
app_home = dir_home + "/bin"
3737
app_bin = app_home + "/scrivenvar.jar"
3838
...
9292
        type( c )
9393
        random_wait()
94
95
# -----------------------------------------------------------------------------
96
# Type a line of text followed by typing the ENTER key.
97
# -----------------------------------------------------------------------------
98
def typeln( text ):
99
    typer( text )
100
    enter()
94101
95102
# -----------------------------------------------------------------------------
...
175182
# Writes a heading to the document using the given text value as the content.
176183
# -----------------------------------------------------------------------------
177
def header( text ):
184
def heading( text ):
178185
    typer( "# " + text )
179186
    paragraph()
M src/main/java/com/scrivenvar/MainWindow.java
149149
    exportDefinitions( getDefinitionPath() );
150150
    interpolateResolvedMap();
151
    resetProcessors();
151152
    renderActiveTab();
152153
  };
M src/main/java/com/scrivenvar/preferences/UserPreferences.java
3030
import com.dlsc.formsfx.model.structure.StringField;
3131
import com.dlsc.preferencesfx.PreferencesFx;
32
import com.dlsc.preferencesfx.PreferencesFxEvent;
3233
import com.dlsc.preferencesfx.model.Category;
3334
import com.dlsc.preferencesfx.model.Group;
3435
import com.dlsc.preferencesfx.model.Setting;
3536
import com.scrivenvar.Services;
3637
import com.scrivenvar.service.Settings;
3738
import javafx.beans.property.ObjectProperty;
3839
import javafx.beans.property.SimpleObjectProperty;
3940
import javafx.beans.property.SimpleStringProperty;
4041
import javafx.beans.property.StringProperty;
42
import javafx.event.EventHandler;
4143
import javafx.scene.Node;
4244
import javafx.scene.control.Label;
...
194196
  private Node label( final String key, final boolean interpolate ) {
195197
    return new Label( get( key, interpolate ) );
198
  }
199
200
  /**
201
   * Delegates to the {@link PreferencesFx} event handler for monitoring
202
   * save events.
203
   *
204
   * @param eventHandler The handler to call when the preferences are saved.
205
   */
206
  public void addSaveEventHandler(
207
      final EventHandler<? super PreferencesFxEvent> eventHandler ) {
208
    final var eventType = PreferencesFxEvent.EVENT_PREFERENCES_SAVED;
209
    mPreferencesFx.addEventHandler( eventType, eventHandler );
196210
  }
197211
...
217231
  }
218232
219
  private ObjectProperty<File> rDirectoryProperty() {
233
  public ObjectProperty<File> rDirectoryProperty() {
220234
    return mPropRDirectory;
221235
  }
222236
223237
  public File getRDirectory() {
224238
    return rDirectoryProperty().getValue();
225239
  }
226240
227
  private StringProperty rScriptProperty() {
241
  public StringProperty rScriptProperty() {
228242
    return mPropRScript;
229243
  }
M src/main/java/com/scrivenvar/processors/InlineRProcessor.java
3232
import com.scrivenvar.service.Options;
3333
import com.scrivenvar.service.events.Notifier;
34
import javafx.beans.property.ObjectProperty;
35
import javafx.beans.property.StringProperty;
3436
3537
import javax.script.ScriptEngine;
3638
import javax.script.ScriptEngineManager;
3739
import javax.script.ScriptException;
40
import java.io.File;
3841
import java.nio.file.Path;
3942
import java.util.LinkedHashMap;
4043
import java.util.Map;
44
import java.util.concurrent.atomic.AtomicBoolean;
4145
4246
import static com.scrivenvar.Constants.STATUS_PARSE_ERROR;
4347
import static com.scrivenvar.Messages.get;
4448
import static com.scrivenvar.decorators.RVariableDecorator.PREFIX;
4549
import static com.scrivenvar.decorators.RVariableDecorator.SUFFIX;
4650
import static com.scrivenvar.processors.text.TextReplacementFactory.replace;
4751
import static java.lang.Math.min;
52
import static java.lang.String.format;
4853
4954
/**
...
7883
7984
  private static final int PREFIX_LENGTH = PREFIX.length();
85
86
  private final AtomicBoolean mDirty = new AtomicBoolean( false );
8087
8188
  /**
...
8996
      final Map<String, String> map ) {
9097
    super( processor, map );
98
99
    bootstrapScriptProperty().addListener(
100
        ( ob, oldScript, newScript ) -> setDirty( true ) );
101
    workingDirectoryProperty().addListener(
102
        ( ob, oldScript, newScript ) -> setDirty( true ) );
103
104
    getUserPreferences().addSaveEventHandler( ( handler ) -> {
105
      if( isDirty() ) {
106
        init();
107
        setDirty( false );
108
      }
109
    } );
110
91111
    init();
92112
  }
93113
94114
  /**
95
   * Initialises the R code so that R can find imported libraries.
115
   * Initialises the R code so that R can find imported libraries. Note that
116
   * any existing R functionality will not be overwritten if this method is
117
   * called multiple times.
96118
   */
97119
  private void init() {
98
    try {
99
      final Path wd = getWorkingDirectory();
100
      final String dir = wd.toString().replace( '\\', '/' );
101
      final Map<String, String> map = getDefinitions();
102
      map.put( "$application.r.working.directory$", dir );
120
    getNotifier().clear();
103121
104
      final String bootstrap = getBootstrapScript();
122
    try {
123
      final var bootstrap = getBootstrapScript();
105124
106125
      if( !bootstrap.isBlank() ) {
126
        final var wd = getWorkingDirectory();
127
        final var dir = wd.toString().replace( '\\', '/' );
128
        final var map = getDefinitions();
129
        map.put( "$application.r.working.directory$", dir );
130
107131
        eval( replace( bootstrap, map ) );
108132
      }
109133
    } catch( final Exception ex ) {
110134
      getNotifier().notify( ex );
111135
    }
136
  }
137
138
  /**
139
   * Sets the dirty flag to indicate that the bootstrap script or working
140
   * directory has been modified. Upon saving the preferences, if this flag
141
   * is true, then {@link #init()} will be called to reload the R environment.
142
   *
143
   * @param dirty Set to true to reload changes upon closing preferences.
144
   */
145
  private void setDirty( final boolean dirty ) {
146
    mDirty.set( dirty );
147
  }
148
149
  /**
150
   * Answers whether R-related settings have been modified.
151
   *
152
   * @return {@code true} when the settings have changed.
153
   */
154
  private boolean isDirty() {
155
    return mDirty.get();
112156
  }
113157
...
123167
  @Override
124168
  public String process( final String text ) {
169
    getNotifier().clear();
170
125171
    final int length = text.length();
126172
...
197243
      return getScriptEngine().eval( r );
198244
    } catch( final ScriptException ex ) {
199
      getNotifier().notify( ex );
200
      return "";
245
      final String expr = r.substring( 0, min( r.length(), 30 ) );
246
      final String msg = format(
247
          "Error with [%s...]: %s", expr, ex.getMessage() );
248
      getNotifier().notify( msg );
201249
    }
250
251
    return "";
202252
  }
203253
204254
  /**
205
   * This will return the given path if not null, otherwise it will return
206
   * the path to the user's directory.
255
   * Return the given path if not {@code null}, otherwise return the path to
256
   * the user's directory.
207257
   *
208258
   * @return A non-null path.
209259
   */
210260
  private Path getWorkingDirectory() {
211261
    return getUserPreferences().getRDirectory().toPath();
262
  }
263
264
  private ObjectProperty<File> workingDirectoryProperty() {
265
    return getUserPreferences().rDirectoryProperty();
212266
  }
213267
214268
  /**
215269
   * Loads the R init script from the application's persisted preferences.
216270
   *
217
   * @return A non-null String, possibly empty.
271
   * @return A non-null string, possibly empty.
218272
   */
219273
  private String getBootstrapScript() {
220274
    return getUserPreferences().getRScript();
275
  }
276
277
  private StringProperty bootstrapScriptProperty() {
278
    return getUserPreferences().rScriptProperty();
221279
  }
222280
M src/main/resources/com/scrivenvar/messages.properties
2525
Main.menu.edit.copy=_Copy
2626
Main.menu.edit.paste=_Paste
27
Main.menu.edit.selectAll=Select _All
2728
Main.menu.edit.find=_Find
2829
Main.menu.edit.find.next=Find _Next