Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
M CREDITS.md
66
* Mikael Grev: [MigLayout](http://www.miglayout.com/)
77
* Tom Eugelink: [MigPane](https://github.com/mikaelgrev/miglayout/blob/master/javafx/src/main/java/org/tbee/javafx/scene/layout/fxml/MigPane.java)
8
* Vladimir Schneider: [flexmark](https://website.com)
98
* Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx)
10
* Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet)
11
* David Croft, [File Preferences](https://github.com/eis/simple-suomi24-java-client/tree/master/src/main/java/net/infotrek/util/prefs)
9
* Dieter Holz, [PreferencesFX](https://github.com/dlsc-software-consulting-gmbh/PreferencesFX)
10
* David Croft, [File Preferences](http://www.davidc.net/programming/java/java-preferences-using-file-backing-store)
1211
* Alex Bertram, [Renjin](https://www.renjin.org/)
12
* Vladimir Schneider: [flexmark](https://github.com/vsch/flexmark-java)
1313
* Michael Kay, [XSLT Processor](http://www.saxonica.com/)
14
* Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet)
1415
1516
M R/README.md
55
* pluralize.R
66
* possessive.R
7
* conversion.R
8
* csv.R
79
810
# pluralize.R
A R/bootstrap.R
1
setwd( '$application.r.working.directory$' )
2
assign( "anchor", '$date.anchor$', envir = .GlobalEnv )
3
4
source( 'pluralize.R' )
5
source( 'possessive.R' )
6
source( 'conversion.R' )
7
source( 'csv.R' )
8
19
M README.md
4040
* XML document transformation using XSLT3 or older
4141
* Platform independent (Windows, Linux, MacOS)
42
* Spellcheck while typing
4243
* R integration
4344
...
5253
## Future Features
5354
54
* Spell check
5555
* Search and replace using variables
5656
* Re-organize variable names
M build.gradle
9090
  implementation 'org.apache.xmlgraphics:batik-xml:1.13'
9191
92
  // Spelling implementation
92
  // Spelling
9393
  implementation fileTree(include: ['**/*.jar'], dir: 'libs')
9494
M images/screenshot.png
Binary file
A licenses/FILE-PREFERENCES.md
1
Released into the Public Domain by David Croft.
2
3
http://www.davidc.net/programming/java/java-preferences-using-file-backing-store
4
http://creativecommons.org/publicdomain/zero/1.0/
5
6
CC0 1.0 Universal (CC0 1.0)
7
8
Public Domain Dedication
9
10
This is a human-readable summary of the Legal Code (read the full text).
11
12
Disclaimer
13
14
No Copyright
15
16
* The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
17
18
* You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. See Other Information below.
19
20
This license is acceptable for Free Cultural Works.
21
22
Other Information
23
24
* In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights.
25
* Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law.
26
* When using or citing the work, you should not imply endorsement by the author or the affirmer.
27
128
A licenses/FLEXMARK.md
1
Copyright (c) 2015-2016, Atlassian Pty Ltd
2
All rights reserved.
3
4
Copyright (c) 2016-2018, Vladimir Schneider,
5
All rights reserved.
6
7
Redistribution and use in source and binary forms, with or without
8
modification, are permitted provided that the following conditions are met:
9
10
* Redistributions of source code must retain the above copyright notice, this
11
  list of conditions and the following disclaimer.
12
13
* Redistributions in binary form must reproduce the above copyright notice,
14
  this list of conditions and the following disclaimer in the documentation
15
  and/or other materials provided with the distribution.
16
17
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
127
A licenses/FLOWLESS.md
1
Copyright (c) 2014, TomasMikula
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are met:
6
7
* Redistributions of source code must retain the above copyright notice, this
8
  list of conditions and the following disclaimer.
9
10
* Redistributions in binary form must reproduce the above copyright notice,
11
  this list of conditions and the following disclaimer in the documentation
12
  and/or other materials provided with the distribution.
13
14
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
124
A licenses/FONT-AWESOME-FX.txt
11
2
                                 Apache License
3
                           Version 2.0, January 2004
4
                        http://www.apache.org/licenses/
5
6
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
8
   1. Definitions.
9
10
      "License" shall mean the terms and conditions for use, reproduction,
11
      and distribution as defined by Sections 1 through 9 of this document.
12
13
      "Licensor" shall mean the copyright owner or entity authorized by
14
      the copyright owner that is granting the License.
15
16
      "Legal Entity" shall mean the union of the acting entity and all
17
      other entities that control, are controlled by, or are under common
18
      control with that entity. For the purposes of this definition,
19
      "control" means (i) the power, direct or indirect, to cause the
20
      direction or management of such entity, whether by contract or
21
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
      outstanding shares, or (iii) beneficial ownership of such entity.
23
24
      "You" (or "Your") shall mean an individual or Legal Entity
25
      exercising permissions granted by this License.
26
27
      "Source" form shall mean the preferred form for making modifications,
28
      including but not limited to software source code, documentation
29
      source, and configuration files.
30
31
      "Object" form shall mean any form resulting from mechanical
32
      transformation or translation of a Source form, including but
33
      not limited to compiled object code, generated documentation,
34
      and conversions to other media types.
35
36
      "Work" shall mean the work of authorship, whether in Source or
37
      Object form, made available under the License, as indicated by a
38
      copyright notice that is included in or attached to the work
39
      (an example is provided in the Appendix below).
40
41
      "Derivative Works" shall mean any work, whether in Source or Object
42
      form, that is based on (or derived from) the Work and for which the
43
      editorial revisions, annotations, elaborations, or other modifications
44
      represent, as a whole, an original work of authorship. For the purposes
45
      of this License, Derivative Works shall not include works that remain
46
      separable from, or merely link (or bind by name) to the interfaces of,
47
      the Work and Derivative Works thereof.
48
49
      "Contribution" shall mean any work of authorship, including
50
      the original version of the Work and any modifications or additions
51
      to that Work or Derivative Works thereof, that is intentionally
52
      submitted to Licensor for inclusion in the Work by the copyright owner
53
      or by an individual or Legal Entity authorized to submit on behalf of
54
      the copyright owner. For the purposes of this definition, "submitted"
55
      means any form of electronic, verbal, or written communication sent
56
      to the Licensor or its representatives, including but not limited to
57
      communication on electronic mailing lists, source code control systems,
58
      and issue tracking systems that are managed by, or on behalf of, the
59
      Licensor for the purpose of discussing and improving the Work, but
60
      excluding communication that is conspicuously marked or otherwise
61
      designated in writing by the copyright owner as "Not a Contribution."
62
63
      "Contributor" shall mean Licensor and any individual or Legal Entity
64
      on behalf of whom a Contribution has been received by Licensor and
65
      subsequently incorporated within the Work.
66
67
   2. Grant of Copyright License. Subject to the terms and conditions of
68
      this License, each Contributor hereby grants to You a perpetual,
69
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
      copyright license to reproduce, prepare Derivative Works of,
71
      publicly display, publicly perform, sublicense, and distribute the
72
      Work and such Derivative Works in Source or Object form.
73
74
   3. Grant of Patent License. Subject to the terms and conditions of
75
      this License, each Contributor hereby grants to You a perpetual,
76
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
      (except as stated in this section) patent license to make, have made,
78
      use, offer to sell, sell, import, and otherwise transfer the Work,
79
      where such license applies only to those patent claims licensable
80
      by such Contributor that are necessarily infringed by their
81
      Contribution(s) alone or by combination of their Contribution(s)
82
      with the Work to which such Contribution(s) was submitted. If You
83
      institute patent litigation against any entity (including a
84
      cross-claim or counterclaim in a lawsuit) alleging that the Work
85
      or a Contribution incorporated within the Work constitutes direct
86
      or contributory patent infringement, then any patent licenses
87
      granted to You under this License for that Work shall terminate
88
      as of the date such litigation is filed.
89
90
   4. Redistribution. You may reproduce and distribute copies of the
91
      Work or Derivative Works thereof in any medium, with or without
92
      modifications, and in Source or Object form, provided that You
93
      meet the following conditions:
94
95
      (a) You must give any other recipients of the Work or
96
          Derivative Works a copy of this License; and
97
98
      (b) You must cause any modified files to carry prominent notices
99
          stating that You changed the files; and
100
101
      (c) You must retain, in the Source form of any Derivative Works
102
          that You distribute, all copyright, patent, trademark, and
103
          attribution notices from the Source form of the Work,
104
          excluding those notices that do not pertain to any part of
105
          the Derivative Works; and
106
107
      (d) If the Work includes a "NOTICE" text file as part of its
108
          distribution, then any Derivative Works that You distribute must
109
          include a readable copy of the attribution notices contained
110
          within such NOTICE file, excluding those notices that do not
111
          pertain to any part of the Derivative Works, in at least one
112
          of the following places: within a NOTICE text file distributed
113
          as part of the Derivative Works; within the Source form or
114
          documentation, if provided along with the Derivative Works; or,
115
          within a display generated by the Derivative Works, if and
116
          wherever such third-party notices normally appear. The contents
117
          of the NOTICE file are for informational purposes only and
118
          do not modify the License. You may add Your own attribution
119
          notices within Derivative Works that You distribute, alongside
120
          or as an addendum to the NOTICE text from the Work, provided
121
          that such additional attribution notices cannot be construed
122
          as modifying the License.
123
124
      You may add Your own copyright statement to Your modifications and
125
      may provide additional or different license terms and conditions
126
      for use, reproduction, or distribution of Your modifications, or
127
      for any such Derivative Works as a whole, provided Your use,
128
      reproduction, and distribution of the Work otherwise complies with
129
      the conditions stated in this License.
130
131
   5. Submission of Contributions. Unless You explicitly state otherwise,
132
      any Contribution intentionally submitted for inclusion in the Work
133
      by You to the Licensor shall be under the terms and conditions of
134
      this License, without any additional terms or conditions.
135
      Notwithstanding the above, nothing herein shall supersede or modify
136
      the terms of any separate license agreement you may have executed
137
      with Licensor regarding such Contributions.
138
139
   6. Trademarks. This License does not grant permission to use the trade
140
      names, trademarks, service marks, or product names of the Licensor,
141
      except as required for reasonable and customary use in describing the
142
      origin of the Work and reproducing the content of the NOTICE file.
143
144
   7. Disclaimer of Warranty. Unless required by applicable law or
145
      agreed to in writing, Licensor provides the Work (and each
146
      Contributor provides its Contributions) on an "AS IS" BASIS,
147
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
      implied, including, without limitation, any warranties or conditions
149
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
      PARTICULAR PURPOSE. You are solely responsible for determining the
151
      appropriateness of using or redistributing the Work and assume any
152
      risks associated with Your exercise of permissions under this License.
153
154
   8. Limitation of Liability. In no event and under no legal theory,
155
      whether in tort (including negligence), contract, or otherwise,
156
      unless required by applicable law (such as deliberate and grossly
157
      negligent acts) or agreed to in writing, shall any Contributor be
158
      liable to You for damages, including any direct, indirect, special,
159
      incidental, or consequential damages of any character arising as a
160
      result of this License or out of the use or inability to use the
161
      Work (including but not limited to damages for loss of goodwill,
162
      work stoppage, computer failure or malfunction, or any and all
163
      other commercial damages or losses), even if such Contributor
164
      has been advised of the possibility of such damages.
165
166
   9. Accepting Warranty or Additional Liability. While redistributing
167
      the Work or Derivative Works thereof, You may choose to offer,
168
      and charge a fee for, acceptance of support, warranty, indemnity,
169
      or other liability obligations and/or rights consistent with this
170
      License. However, in accepting such obligations, You may act only
171
      on Your own behalf and on Your sole responsibility, not on behalf
172
      of any other Contributor, and only if You agree to indemnify,
173
      defend, and hold each Contributor harmless for any liability
174
      incurred by, or claims asserted against, such Contributor by reason
175
      of your accepting any such warranty or additional liability.
176
177
   END OF TERMS AND CONDITIONS
178
179
   APPENDIX: How to apply the Apache License to your work.
180
181
      To apply the Apache License to your work, attach the following
182
      boilerplate notice, with the fields enclosed by brackets "[]"
183
      replaced with your own identifying information. (Don't include
184
      the brackets!)  The text should be enclosed in the appropriate
185
      comment syntax for the file format. We also recommend that a
186
      file or class name and description of purpose be included on the
187
      same "printed page" as the copyright notice for easier
188
      identification within third-party archives.
189
190
   Copyright [yyyy] [name of copyright owner]
191
192
   Licensed under the Apache License, Version 2.0 (the "License");
193
   you may not use this file except in compliance with the License.
194
   You may obtain a copy of the License at
195
196
       http://www.apache.org/licenses/LICENSE-2.0
197
198
   Unless required by applicable law or agreed to in writing, software
199
   distributed under the License is distributed on an "AS IS" BASIS,
200
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
   See the License for the specific language governing permissions and
202
   limitations under the License.
203
A licenses/JSYMSPELL.md
1
MIT License
2
3
Copyright (c) 2019 Raul Garcia
4
5
Permission is hereby granted, free of charge, to any person obtaining a copy
6
of this software and associated documentation files (the "Software"), to deal
7
in the Software without restriction, including without limitation the rights
8
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
copies of the Software, and to permit persons to whom the Software is
10
furnished to do so, subject to the following conditions:
11
12
The above copyright notice and this permission notice shall be included in all
13
copies or substantial portions of the Software.
14
15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
SOFTWARE.
122
A licenses/JUNIVERSAL-CHARDET.md
1
Version: MPL 1.1/GPL 2.0/LGPL 2.1
2
3
The contents of this file are subject to the Mozilla Public License Version
4
1.1 (the "License"); you may not use this file except in compliance with
5
the License. You may obtain a copy of the License at
6
http://www.mozilla.org/MPL/
7
8
Software distributed under the License is distributed on an "AS IS" basis,
9
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10
for the specific language governing rights and limitations under the
11
License.
12
13
The Original Code is Mozilla Universal charset detector code.
14
15
The Initial Developer of the Original Code is
16
Netscape Communications Corporation.
17
Portions created by the Initial Developer are Copyright (C) 2001
18
the Initial Developer. All Rights Reserved.
19
20
Contributor(s):
21
        Shy Shalom <shooshX@gmail.com>
22
        Kohei TAKETA <k-tak@void.in> (Java port)
23
24
Alternatively, the contents of this file may be used under the terms of
25
either the GNU General Public License Version 2 or later (the "GPL"), or
26
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27
in which case the provisions of the GPL or the LGPL are applicable instead
28
of those above. If you wish to allow use of your version of this file only
29
under the terms of either the GPL or the LGPL, and not to allow others to
30
use your version of this file under the terms of the MPL, indicate your
31
decision by deleting the provisions above and replace them with the notice
32
and other provisions required by the GPL or the LGPL. If you do not delete
33
the provisions above, a recipient may use your version of this file under
34
the terms of any one of the MPL, the GPL or the LGPL.
35
136
A licenses/MIG-LAYOUT.md
1
Copyright (c) 2000 Mikael Grev
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions
6
are met:
7
1. Redistributions of source code must retain the above copyright
8
   notice, this list of conditions and the following disclaimer.
9
2. Redistributions in binary form must reproduce the above copyright
10
   notice, this list of conditions and the following disclaimer in the
11
   documentation and/or other materials provided with the distribution.
12
3. The name of the author may not be used to endorse or promote products
13
   derived from this software without specific prior written permission.
14
15
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
126
A licenses/PREFERENCES-FX.txt
1
                                 Apache License
2
                           Version 2.0, January 2004
3
                        http://www.apache.org/licenses/
4
5
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
7
   1. Definitions.
8
9
      "License" shall mean the terms and conditions for use, reproduction,
10
      and distribution as defined by Sections 1 through 9 of this document.
11
12
      "Licensor" shall mean the copyright owner or entity authorized by
13
      the copyright owner that is granting the License.
14
15
      "Legal Entity" shall mean the union of the acting entity and all
16
      other entities that control, are controlled by, or are under common
17
      control with that entity. For the purposes of this definition,
18
      "control" means (i) the power, direct or indirect, to cause the
19
      direction or management of such entity, whether by contract or
20
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
      outstanding shares, or (iii) beneficial ownership of such entity.
22
23
      "You" (or "Your") shall mean an individual or Legal Entity
24
      exercising permissions granted by this License.
25
26
      "Source" form shall mean the preferred form for making modifications,
27
      including but not limited to software source code, documentation
28
      source, and configuration files.
29
30
      "Object" form shall mean any form resulting from mechanical
31
      transformation or translation of a Source form, including but
32
      not limited to compiled object code, generated documentation,
33
      and conversions to other media types.
34
35
      "Work" shall mean the work of authorship, whether in Source or
36
      Object form, made available under the License, as indicated by a
37
      copyright notice that is included in or attached to the work
38
      (an example is provided in the Appendix below).
39
40
      "Derivative Works" shall mean any work, whether in Source or Object
41
      form, that is based on (or derived from) the Work and for which the
42
      editorial revisions, annotations, elaborations, or other modifications
43
      represent, as a whole, an original work of authorship. For the purposes
44
      of this License, Derivative Works shall not include works that remain
45
      separable from, or merely link (or bind by name) to the interfaces of,
46
      the Work and Derivative Works thereof.
47
48
      "Contribution" shall mean any work of authorship, including
49
      the original version of the Work and any modifications or additions
50
      to that Work or Derivative Works thereof, that is intentionally
51
      submitted to Licensor for inclusion in the Work by the copyright owner
52
      or by an individual or Legal Entity authorized to submit on behalf of
53
      the copyright owner. For the purposes of this definition, "submitted"
54
      means any form of electronic, verbal, or written communication sent
55
      to the Licensor or its representatives, including but not limited to
56
      communication on electronic mailing lists, source code control systems,
57
      and issue tracking systems that are managed by, or on behalf of, the
58
      Licensor for the purpose of discussing and improving the Work, but
59
      excluding communication that is conspicuously marked or otherwise
60
      designated in writing by the copyright owner as "Not a Contribution."
61
62
      "Contributor" shall mean Licensor and any individual or Legal Entity
63
      on behalf of whom a Contribution has been received by Licensor and
64
      subsequently incorporated within the Work.
65
66
   2. Grant of Copyright License. Subject to the terms and conditions of
67
      this License, each Contributor hereby grants to You a perpetual,
68
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
      copyright license to reproduce, prepare Derivative Works of,
70
      publicly display, publicly perform, sublicense, and distribute the
71
      Work and such Derivative Works in Source or Object form.
72
73
   3. Grant of Patent License. Subject to the terms and conditions of
74
      this License, each Contributor hereby grants to You a perpetual,
75
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
      (except as stated in this section) patent license to make, have made,
77
      use, offer to sell, sell, import, and otherwise transfer the Work,
78
      where such license applies only to those patent claims licensable
79
      by such Contributor that are necessarily infringed by their
80
      Contribution(s) alone or by combination of their Contribution(s)
81
      with the Work to which such Contribution(s) was submitted. If You
82
      institute patent litigation against any entity (including a
83
      cross-claim or counterclaim in a lawsuit) alleging that the Work
84
      or a Contribution incorporated within the Work constitutes direct
85
      or contributory patent infringement, then any patent licenses
86
      granted to You under this License for that Work shall terminate
87
      as of the date such litigation is filed.
88
89
   4. Redistribution. You may reproduce and distribute copies of the
90
      Work or Derivative Works thereof in any medium, with or without
91
      modifications, and in Source or Object form, provided that You
92
      meet the following conditions:
93
94
      (a) You must give any other recipients of the Work or
95
          Derivative Works a copy of this License; and
96
97
      (b) You must cause any modified files to carry prominent notices
98
          stating that You changed the files; and
99
100
      (c) You must retain, in the Source form of any Derivative Works
101
          that You distribute, all copyright, patent, trademark, and
102
          attribution notices from the Source form of the Work,
103
          excluding those notices that do not pertain to any part of
104
          the Derivative Works; and
105
106
      (d) If the Work includes a "NOTICE" text file as part of its
107
          distribution, then any Derivative Works that You distribute must
108
          include a readable copy of the attribution notices contained
109
          within such NOTICE file, excluding those notices that do not
110
          pertain to any part of the Derivative Works, in at least one
111
          of the following places: within a NOTICE text file distributed
112
          as part of the Derivative Works; within the Source form or
113
          documentation, if provided along with the Derivative Works; or,
114
          within a display generated by the Derivative Works, if and
115
          wherever such third-party notices normally appear. The contents
116
          of the NOTICE file are for informational purposes only and
117
          do not modify the License. You may add Your own attribution
118
          notices within Derivative Works that You distribute, alongside
119
          or as an addendum to the NOTICE text from the Work, provided
120
          that such additional attribution notices cannot be construed
121
          as modifying the License.
122
123
      You may add Your own copyright statement to Your modifications and
124
      may provide additional or different license terms and conditions
125
      for use, reproduction, or distribution of Your modifications, or
126
      for any such Derivative Works as a whole, provided Your use,
127
      reproduction, and distribution of the Work otherwise complies with
128
      the conditions stated in this License.
129
130
   5. Submission of Contributions. Unless You explicitly state otherwise,
131
      any Contribution intentionally submitted for inclusion in the Work
132
      by You to the Licensor shall be under the terms and conditions of
133
      this License, without any additional terms or conditions.
134
      Notwithstanding the above, nothing herein shall supersede or modify
135
      the terms of any separate license agreement you may have executed
136
      with Licensor regarding such Contributions.
137
138
   6. Trademarks. This License does not grant permission to use the trade
139
      names, trademarks, service marks, or product names of the Licensor,
140
      except as required for reasonable and customary use in describing the
141
      origin of the Work and reproducing the content of the NOTICE file.
142
143
   7. Disclaimer of Warranty. Unless required by applicable law or
144
      agreed to in writing, Licensor provides the Work (and each
145
      Contributor provides its Contributions) on an "AS IS" BASIS,
146
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
      implied, including, without limitation, any warranties or conditions
148
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
      PARTICULAR PURPOSE. You are solely responsible for determining the
150
      appropriateness of using or redistributing the Work and assume any
151
      risks associated with Your exercise of permissions under this License.
152
153
   8. Limitation of Liability. In no event and under no legal theory,
154
      whether in tort (including negligence), contract, or otherwise,
155
      unless required by applicable law (such as deliberate and grossly
156
      negligent acts) or agreed to in writing, shall any Contributor be
157
      liable to You for damages, including any direct, indirect, special,
158
      incidental, or consequential damages of any character arising as a
159
      result of this License or out of the use or inability to use the
160
      Work (including but not limited to damages for loss of goodwill,
161
      work stoppage, computer failure or malfunction, or any and all
162
      other commercial damages or losses), even if such Contributor
163
      has been advised of the possibility of such damages.
164
165
   9. Accepting Warranty or Additional Liability. While redistributing
166
      the Work or Derivative Works thereof, You may choose to offer,
167
      and charge a fee for, acceptance of support, warranty, indemnity,
168
      or other liability obligations and/or rights consistent with this
169
      License. However, in accepting such obligations, You may act only
170
      on Your own behalf and on Your sole responsibility, not on behalf
171
      of any other Contributor, and only if You agree to indemnify,
172
      defend, and hold each Contributor harmless for any liability
173
      incurred by, or claims asserted against, such Contributor by reason
174
      of your accepting any such warranty or additional liability.
175
176
   END OF TERMS AND CONDITIONS
177
178
   APPENDIX: How to apply the Apache License to your work.
179
180
      To apply the Apache License to your work, attach the following
181
      boilerplate notice, with the fields enclosed by brackets "{}"
182
      replaced with your own identifying information. (Don't include
183
      the brackets!)  The text should be enclosed in the appropriate
184
      comment syntax for the file format. We also recommend that a
185
      file or class name and description of purpose be included on the
186
      same "printed page" as the copyright notice for easier
187
      identification within third-party archives.
188
189
   Copyright {yyyy} {name of copyright owner}
190
191
   Licensed under the Apache License, Version 2.0 (the "License");
192
   you may not use this file except in compliance with the License.
193
   You may obtain a copy of the License at
194
195
       http://www.apache.org/licenses/LICENSE-2.0
196
197
   Unless required by applicable law or agreed to in writing, software
198
   distributed under the License is distributed on an "AS IS" BASIS,
199
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
   See the License for the specific language governing permissions and
201
   limitations under the License.
1202
A licenses/REACT-FX.md
1
Copyright (c) 2013-2014, Tomas Mikula
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
6
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
8
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
10
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
111
A licenses/RENJIN.txt
1
		    GNU GENERAL PUBLIC LICENSE
2
		       Version 2, June 1991
3
4
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5
                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
6
 Everyone is permitted to copy and distribute verbatim copies
7
 of this license document, but changing it is not allowed.
8
9
			    Preamble
10
11
  The licenses for most software are designed to take away your
12
freedom to share and change it.  By contrast, the GNU General Public
13
License is intended to guarantee your freedom to share and change free
14
software--to make sure the software is free for all its users.  This
15
General Public License applies to most of the Free Software
16
Foundation's software and to any other program whose authors commit to
17
using it.  (Some other Free Software Foundation software is covered by
18
the GNU Library General Public License instead.)  You can apply it to
19
your programs, too.
20
21
  When we speak of free software, we are referring to freedom, not
22
price.  Our General Public Licenses are designed to make sure that you
23
have the freedom to distribute copies of free software (and charge for
24
this service if you wish), that you receive source code or can get it
25
if you want it, that you can change the software or use pieces of it
26
in new free programs; and that you know you can do these things.
27
28
  To protect your rights, we need to make restrictions that forbid
29
anyone to deny you these rights or to ask you to surrender the rights.
30
These restrictions translate to certain responsibilities for you if you
31
distribute copies of the software, or if you modify it.
32
33
  For example, if you distribute copies of such a program, whether
34
gratis or for a fee, you must give the recipients all the rights that
35
you have.  You must make sure that they, too, receive or can get the
36
source code.  And you must show them these terms so they know their
37
rights.
38
39
  We protect your rights with two steps: (1) copyright the software, and
40
(2) offer you this license which gives you legal permission to copy,
41
distribute and/or modify the software.
42
43
  Also, for each author's protection and ours, we want to make certain
44
that everyone understands that there is no warranty for this free
45
software.  If the software is modified by someone else and passed on, we
46
want its recipients to know that what they have is not the original, so
47
that any problems introduced by others will not reflect on the original
48
authors' reputations.
49
50
  Finally, any free program is threatened constantly by software
51
patents.  We wish to avoid the danger that redistributors of a free
52
program will individually obtain patent licenses, in effect making the
53
program proprietary.  To prevent this, we have made it clear that any
54
patent must be licensed for everyone's free use or not licensed at all.
55
56
  The precise terms and conditions for copying, distribution and
57
modification follow.
58

59
		    GNU GENERAL PUBLIC LICENSE
60
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
62
  0. This License applies to any program or other work which contains
63
a notice placed by the copyright holder saying it may be distributed
64
under the terms of this General Public License.  The "Program", below,
65
refers to any such program or work, and a "work based on the Program"
66
means either the Program or any derivative work under copyright law:
67
that is to say, a work containing the Program or a portion of it,
68
either verbatim or with modifications and/or translated into another
69
language.  (Hereinafter, translation is included without limitation in
70
the term "modification".)  Each licensee is addressed as "you".
71
72
Activities other than copying, distribution and modification are not
73
covered by this License; they are outside its scope.  The act of
74
running the Program is not restricted, and the output from the Program
75
is covered only if its contents constitute a work based on the
76
Program (independent of having been made by running the Program).
77
Whether that is true depends on what the Program does.
78
79
  1. You may copy and distribute verbatim copies of the Program's
80
source code as you receive it, in any medium, provided that you
81
conspicuously and appropriately publish on each copy an appropriate
82
copyright notice and disclaimer of warranty; keep intact all the
83
notices that refer to this License and to the absence of any warranty;
84
and give any other recipients of the Program a copy of this License
85
along with the Program.
86
87
You may charge a fee for the physical act of transferring a copy, and
88
you may at your option offer warranty protection in exchange for a fee.
89
90
  2. You may modify your copy or copies of the Program or any portion
91
of it, thus forming a work based on the Program, and copy and
92
distribute such modifications or work under the terms of Section 1
93
above, provided that you also meet all of these conditions:
94
95
    a) You must cause the modified files to carry prominent notices
96
    stating that you changed the files and the date of any change.
97
98
    b) You must cause any work that you distribute or publish, that in
99
    whole or in part contains or is derived from the Program or any
100
    part thereof, to be licensed as a whole at no charge to all third
101
    parties under the terms of this License.
102
103
    c) If the modified program normally reads commands interactively
104
    when run, you must cause it, when started running for such
105
    interactive use in the most ordinary way, to print or display an
106
    announcement including an appropriate copyright notice and a
107
    notice that there is no warranty (or else, saying that you provide
108
    a warranty) and that users may redistribute the program under
109
    these conditions, and telling the user how to view a copy of this
110
    License.  (Exception: if the Program itself is interactive but
111
    does not normally print such an announcement, your work based on
112
    the Program is not required to print an announcement.)
113

114
These requirements apply to the modified work as a whole.  If
115
identifiable sections of that work are not derived from the Program,
116
and can be reasonably considered independent and separate works in
117
themselves, then this License, and its terms, do not apply to those
118
sections when you distribute them as separate works.  But when you
119
distribute the same sections as part of a whole which is a work based
120
on the Program, the distribution of the whole must be on the terms of
121
this License, whose permissions for other licensees extend to the
122
entire whole, and thus to each and every part regardless of who wrote it.
123
124
Thus, it is not the intent of this section to claim rights or contest
125
your rights to work written entirely by you; rather, the intent is to
126
exercise the right to control the distribution of derivative or
127
collective works based on the Program.
128
129
In addition, mere aggregation of another work not based on the Program
130
with the Program (or with a work based on the Program) on a volume of
131
a storage or distribution medium does not bring the other work under
132
the scope of this License.
133
134
  3. You may copy and distribute the Program (or a work based on it,
135
under Section 2) in object code or executable form under the terms of
136
Sections 1 and 2 above provided that you also do one of the following:
137
138
    a) Accompany it with the complete corresponding machine-readable
139
    source code, which must be distributed under the terms of Sections
140
    1 and 2 above on a medium customarily used for software interchange; or,
141
142
    b) Accompany it with a written offer, valid for at least three
143
    years, to give any third party, for a charge no more than your
144
    cost of physically performing source distribution, a complete
145
    machine-readable copy of the corresponding source code, to be
146
    distributed under the terms of Sections 1 and 2 above on a medium
147
    customarily used for software interchange; or,
148
149
    c) Accompany it with the information you received as to the offer
150
    to distribute corresponding source code.  (This alternative is
151
    allowed only for noncommercial distribution and only if you
152
    received the program in object code or executable form with such
153
    an offer, in accord with Subsection b above.)
154
155
The source code for a work means the preferred form of the work for
156
making modifications to it.  For an executable work, complete source
157
code means all the source code for all modules it contains, plus any
158
associated interface definition files, plus the scripts used to
159
control compilation and installation of the executable.  However, as a
160
special exception, the source code distributed need not include
161
anything that is normally distributed (in either source or binary
162
form) with the major components (compiler, kernel, and so on) of the
163
operating system on which the executable runs, unless that component
164
itself accompanies the executable.
165
166
If distribution of executable or object code is made by offering
167
access to copy from a designated place, then offering equivalent
168
access to copy the source code from the same place counts as
169
distribution of the source code, even though third parties are not
170
compelled to copy the source along with the object code.
171

172
  4. You may not copy, modify, sublicense, or distribute the Program
173
except as expressly provided under this License.  Any attempt
174
otherwise to copy, modify, sublicense or distribute the Program is
175
void, and will automatically terminate your rights under this License.
176
However, parties who have received copies, or rights, from you under
177
this License will not have their licenses terminated so long as such
178
parties remain in full compliance.
179
180
  5. You are not required to accept this License, since you have not
181
signed it.  However, nothing else grants you permission to modify or
182
distribute the Program or its derivative works.  These actions are
183
prohibited by law if you do not accept this License.  Therefore, by
184
modifying or distributing the Program (or any work based on the
185
Program), you indicate your acceptance of this License to do so, and
186
all its terms and conditions for copying, distributing or modifying
187
the Program or works based on it.
188
189
  6. Each time you redistribute the Program (or any work based on the
190
Program), the recipient automatically receives a license from the
191
original licensor to copy, distribute or modify the Program subject to
192
these terms and conditions.  You may not impose any further
193
restrictions on the recipients' exercise of the rights granted herein.
194
You are not responsible for enforcing compliance by third parties to
195
this License.
196
197
  7. If, as a consequence of a court judgment or allegation of patent
198
infringement or for any other reason (not limited to patent issues),
199
conditions are imposed on you (whether by court order, agreement or
200
otherwise) that contradict the conditions of this License, they do not
201
excuse you from the conditions of this License.  If you cannot
202
distribute so as to satisfy simultaneously your obligations under this
203
License and any other pertinent obligations, then as a consequence you
204
may not distribute the Program at all.  For example, if a patent
205
license would not permit royalty-free redistribution of the Program by
206
all those who receive copies directly or indirectly through you, then
207
the only way you could satisfy both it and this License would be to
208
refrain entirely from distribution of the Program.
209
210
If any portion of this section is held invalid or unenforceable under
211
any particular circumstance, the balance of the section is intended to
212
apply and the section as a whole is intended to apply in other
213
circumstances.
214
215
It is not the purpose of this section to induce you to infringe any
216
patents or other property right claims or to contest validity of any
217
such claims; this section has the sole purpose of protecting the
218
integrity of the free software distribution system, which is
219
implemented by public license practices.  Many people have made
220
generous contributions to the wide range of software distributed
221
through that system in reliance on consistent application of that
222
system; it is up to the author/donor to decide if he or she is willing
223
to distribute software through any other system and a licensee cannot
224
impose that choice.
225
226
This section is intended to make thoroughly clear what is believed to
227
be a consequence of the rest of this License.
228

229
  8. If the distribution and/or use of the Program is restricted in
230
certain countries either by patents or by copyrighted interfaces, the
231
original copyright holder who places the Program under this License
232
may add an explicit geographical distribution limitation excluding
233
those countries, so that distribution is permitted only in or among
234
countries not thus excluded.  In such case, this License incorporates
235
the limitation as if written in the body of this License.
236
237
  9. The Free Software Foundation may publish revised and/or new versions
238
of the General Public License from time to time.  Such new versions will
239
be similar in spirit to the present version, but may differ in detail to
240
address new problems or concerns.
241
242
Each version is given a distinguishing version number.  If the Program
243
specifies a version number of this License which applies to it and "any
244
later version", you have the option of following the terms and conditions
245
either of that version or of any later version published by the Free
246
Software Foundation.  If the Program does not specify a version number of
247
this License, you may choose any version ever published by the Free Software
248
Foundation.
249
250
  10. If you wish to incorporate parts of the Program into other free
251
programs whose distribution conditions are different, write to the author
252
to ask for permission.  For software which is copyrighted by the Free
253
Software Foundation, write to the Free Software Foundation; we sometimes
254
make exceptions for this.  Our decision will be guided by the two goals
255
of preserving the free status of all derivatives of our free software and
256
of promoting the sharing and reuse of software generally.
257
258
			    NO WARRANTY
259
260
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
262
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
266
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
267
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
REPAIR OR CORRECTION.
269
270
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
POSSIBILITY OF SUCH DAMAGES.
279
280
		     END OF TERMS AND CONDITIONS
281

282
	    How to Apply These Terms to Your New Programs
283
284
  If you develop a new program, and you want it to be of the greatest
285
possible use to the public, the best way to achieve this is to make it
286
free software which everyone can redistribute and change under these terms.
287
288
  To do so, attach the following notices to the program.  It is safest
289
to attach them to the start of each source file to most effectively
290
convey the exclusion of warranty; and each file should have at least
291
the "copyright" line and a pointer to where the full notice is found.
292
293
    <one line to give the program's name and a brief idea of what it does.>
294
    Copyright (C) <year>  <name of author>
295
296
    This program is free software; you can redistribute it and/or modify
297
    it under the terms of the GNU General Public License as published by
298
    the Free Software Foundation; either version 2 of the License, or
299
    (at your option) any later version.
300
301
    This program is distributed in the hope that it will be useful,
302
    but WITHOUT ANY WARRANTY; without even the implied warranty of
303
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
304
    GNU General Public License for more details.
305
306
    You should have received a copy of the GNU General Public License
307
    along with this program; if not, write to the Free Software
308
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
309
310
311
Also add information on how to contact you by electronic and paper mail.
312
313
If the program is interactive, make it output a short notice like this
314
when it starts in an interactive mode:
315
316
    Gnomovision version 69, Copyright (C) year name of author
317
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318
    This is free software, and you are welcome to redistribute it
319
    under certain conditions; type `show c' for details.
320
321
The hypothetical commands `show w' and `show c' should show the appropriate
322
parts of the General Public License.  Of course, the commands you use may
323
be called something other than `show w' and `show c'; they could even be
324
mouse-clicks or menu items--whatever suits your program.
325
326
You should also get your employer (if you work as a programmer) or your
327
school, if any, to sign a "copyright disclaimer" for the program, if
328
necessary.  Here is a sample; alter the names:
329
330
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
332
333
  <signature of Ty Coon>, 1 April 1989
334
  Ty Coon, President of Vice
335
336
This General Public License does not permit incorporating your program into
337
proprietary programs.  If your program is a subroutine library, you may
338
consider it more useful to permit linking proprietary applications with the
339
library.  If this is what you want to do, use the GNU Library General
340
Public License instead of this License.
1341
A licenses/RICH-TEXT-FX.md
1
Copyright (c) 2013-2017, Tomas Mikula and contributors
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
6
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
8
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
10
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
111
A licenses/SAXON-HE.txt
1
                             Mozilla Public License
2
                                  Version 2.0
3
4
1. Definitions
5
6
   1.1. “Contributor”
7
          means each individual or legal entity that creates, contributes
8
          to the creation of, or owns Covered Software.
9
10
   1.2. “Contributor Version”
11
          means the combination of the Contributions of others (if any)
12
          used by a Contributor and that particular Contributor’s
13
          Contribution.
14
15
   1.3. “Contribution”
16
          means Covered Software of a particular Contributor.
17
18
   1.4. “Covered Software”
19
          means Source Code Form to which the initial Contributor has
20
          attached the notice in Exhibit A, the Executable Form of such
21
          Source Code Form, and Modifications of such Source Code Form, in
22
          each case including portions thereof.
23
24
   1.5. “Incompatible With Secondary Licenses”
25
          means
26
27
         a. that the initial Contributor has attached the notice described
28
            in Exhibit B to the Covered Software; or
29
         b. that the Covered Software was made available under the terms
30
            of version 1.1 or earlier of the License, but not also under
31
            the terms of a Secondary License.
32
33
   1.6. “Executable Form”
34
          means any form of the work other than Source Code Form.
35
36
   1.7. “Larger Work”
37
          means a work that combines Covered Software with other material,
38
          in a separate file or files, that is not Covered Software.
39
40
   1.8. “License”
41
          means this document.
42
43
   1.9. “Licensable”
44
          means having the right to grant, to the maximum extent possible,
45
          whether at the time of the initial grant or subsequently, any
46
          and all of the rights conveyed by this License.
47
48
   1.10. “Modifications”
49
          means any of the following:
50
51
         a. any file in Source Code Form that results from an addition to,
52
            deletion from, or modification of the contents of Covered
53
            Software; or
54
         b. any new file in Source Code Form that contains any Covered
55
            Software.
56
57
   1.11. “Patent Claims” of a Contributor
58
          means any patent claim(s), including without limitation, method,
59
          process, and apparatus claims, in any patent Licensable by such
60
          Contributor that would be infringed, but for the grant of the
61
          License, by the making, using, selling, offering for sale,
62
          having made, import, or transfer of either its Contributions or
63
          its Contributor Version.
64
65
   1.12. “Secondary License”
66
          means either the GNU General Public License, Version 2.0, the
67
          GNU Lesser General Public License, Version 2.1, the GNU Affero
68
          General Public License, Version 3.0, or any later versions of
69
          those licenses.
70
71
   1.13. “Source Code Form”
72
          means the form of the work preferred for making modifications.
73
74
   1.14. “You” (or “Your”)
75
          means an individual or a legal entity exercising rights under
76
          this License. For legal entities, “You” includes any entity that
77
          controls, is controlled by, or is under common control with You.
78
          For purposes of this definition, “control” means (a) the power,
79
          direct or indirect, to cause the direction or management of such
80
          entity, whether by contract or otherwise, or (b) ownership of
81
          more than fifty percent (50%) of the outstanding shares or
82
          beneficial ownership of such entity.
83
84
2. License Grants and Conditions
85
86
  2.1. Grants
87
88
   Each Contributor hereby grants You a world-wide, royalty-free,
89
   non-exclusive license:
90
    a. under intellectual property rights (other than patent or trademark)
91
       Licensable by such Contributor to use, reproduce, make available,
92
       modify, display, perform, distribute, and otherwise exploit its
93
       Contributions, either on an unmodified basis, with Modifications,
94
       or as part of a Larger Work; and
95
    b. under Patent Claims of such Contributor to make, use, sell, offer
96
       for sale, have made, import, and otherwise transfer either its
97
       Contributions or its Contributor Version.
98
99
  2.2. Effective Date
100
101
   The licenses granted in Section 2.1 with respect to any Contribution
102
   become effective for each Contribution on the date the Contributor
103
   first distributes such Contribution.
104
105
  2.3. Limitations on Grant Scope
106
107
   The licenses granted in this Section 2 are the only rights granted
108
   under this License. No additional rights or licenses will be implied
109
   from the distribution or licensing of Covered Software under this
110
   License. Notwithstanding Section 2.1(b) above, no patent license is
111
   granted by a Contributor:
112
    a. for any code that a Contributor has removed from Covered Software;
113
       or
114
    b. for infringements caused by: (i) Your and any other third party’s
115
       modifications of Covered Software, or (ii) the combination of its
116
       Contributions with other software (except as part of its
117
       Contributor Version); or
118
    c. under Patent Claims infringed by Covered Software in the absence of
119
       its Contributions.
120
121
   This License does not grant any rights in the trademarks, service
122
   marks, or logos of any Contributor (except as may be necessary to
123
   comply with the notice requirements in Section 3.4).
124
125
  2.4. Subsequent Licenses
126
127
   No Contributor makes additional grants as a result of Your choice to
128
   distribute the Covered Software under a subsequent version of this
129
   License (see Section 10.2) or under the terms of a Secondary License
130
   (if permitted under the terms of Section 3.3).
131
132
  2.5. Representation
133
134
   Each Contributor represents that the Contributor believes its
135
   Contributions are its original creation(s) or it has sufficient rights
136
   to grant the rights to its Contributions conveyed by this License.
137
138
  2.6. Fair Use
139
140
   This License is not intended to limit any rights You have under
141
   applicable copyright doctrines of fair use, fair dealing, or other
142
   equivalents.
143
144
  2.7. Conditions
145
146
   Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
147
   in Section 2.1.
148
149
3. Responsibilities
150
151
  3.1. Distribution of Source Form
152
153
   All distribution of Covered Software in Source Code Form, including any
154
   Modifications that You create or to which You contribute, must be under
155
   the terms of this License. You must inform recipients that the Source
156
   Code Form of the Covered Software is governed by the terms of this
157
   License, and how they can obtain a copy of this License. You may not
158
   attempt to alter or restrict the recipients’ rights in the Source Code
159
   Form.
160
161
  3.2. Distribution of Executable Form
162
163
   If You distribute Covered Software in Executable Form then:
164
    a. such Covered Software must also be made available in Source Code
165
       Form, as described in Section 3.1, and You must inform recipients
166
       of the Executable Form how they can obtain a copy of such Source
167
       Code Form by reasonable means in a timely manner, at a charge no
168
       more than the cost of distribution to the recipient; and
169
    b. You may distribute such Executable Form under the terms of this
170
       License, or sublicense it under different terms, provided that the
171
       license for the Executable Form does not attempt to limit or alter
172
       the recipients’ rights in the Source Code Form under this License.
173
174
  3.3. Distribution of a Larger Work
175
176
   You may create and distribute a Larger Work under terms of Your choice,
177
   provided that You also comply with the requirements of this License for
178
   the Covered Software. If the Larger Work is a combination of Covered
179
   Software with a work governed by one or more Secondary Licenses, and
180
   the Covered Software is not Incompatible With Secondary Licenses, this
181
   License permits You to additionally distribute such Covered Software
182
   under the terms of such Secondary License(s), so that the recipient of
183
   the Larger Work may, at their option, further distribute the Covered
184
   Software under the terms of either this License or such Secondary
185
   License(s).
186
187
  3.4. Notices
188
189
   You may not remove or alter the substance of any license notices
190
   (including copyright notices, patent notices, disclaimers of warranty,
191
   or limitations of liability) contained within the Source Code Form of
192
   the Covered Software, except that You may alter any license notices to
193
   the extent required to remedy known factual inaccuracies.
194
195
  3.5. Application of Additional Terms
196
197
   You may choose to offer, and to charge a fee for, warranty, support,
198
   indemnity or liability obligations to one or more recipients of Covered
199
   Software. However, You may do so only on Your own behalf, and not on
200
   behalf of any Contributor. You must make it absolutely clear that any
201
   such warranty, support, indemnity, or liability obligation is offered
202
   by You alone, and You hereby agree to indemnify every Contributor for
203
   any liability incurred by such Contributor as a result of warranty,
204
   support, indemnity or liability terms You offer. You may include
205
   additional disclaimers of warranty and limitations of liability
206
   specific to any jurisdiction.
207
208
4. Inability to Comply Due to Statute or Regulation
209
210
   If it is impossible for You to comply with any of the terms of this
211
   License with respect to some or all of the Covered Software due to
212
   statute, judicial order, or regulation then You must: (a) comply with
213
   the terms of this License to the maximum extent possible; and (b)
214
   describe the limitations and the code they affect. Such description
215
   must be placed in a text file included with all distributions of the
216
   Covered Software under this License. Except to the extent prohibited by
217
   statute or regulation, such description must be sufficiently detailed
218
   for a recipient of ordinary skill to be able to understand it.
219
220
5. Termination
221
222
   5.1. The rights granted under this License will terminate automatically
223
   if You fail to comply with any of its terms. However, if You become
224
   compliant, then the rights granted under this License from a particular
225
   Contributor are reinstated (a) provisionally, unless and until such
226
   Contributor explicitly and finally terminates Your grants, and (b) on
227
   an ongoing basis, if such Contributor fails to notify You of the
228
   non-compliance by some reasonable means prior to 60 days after You have
229
   come back into compliance. Moreover, Your grants from a particular
230
   Contributor are reinstated on an ongoing basis if such Contributor
231
   notifies You of the non-compliance by some reasonable means, this is
232
   the first time You have received notice of non-compliance with this
233
   License from such Contributor, and You become compliant prior to 30
234
   days after Your receipt of the notice.
235
236
   5.2. If You initiate litigation against any entity by asserting a
237
   patent infringement claim (excluding declaratory judgment actions,
238
   counter-claims, and cross-claims) alleging that a Contributor Version
239
   directly or indirectly infringes any patent, then the rights granted to
240
   You by any and all Contributors for the Covered Software under
241
   Section 2.1 of this License shall terminate.
242
243
   5.3. In the event of termination under Sections 5.1 or 5.2 above, all
244
   end user license agreements (excluding distributors and resellers)
245
   which have been validly granted by You or Your distributors under this
246
   License prior to termination shall survive termination.
247
248
6. Disclaimer of Warranty
249
250
   Covered Software is provided under this License on an “as is” basis,
251
   without warranty of any kind, either expressed, implied, or statutory,
252
   including, without limitation, warranties that the Covered Software is
253
   free of defects, merchantable, fit for a particular purpose or
254
   non-infringing. The entire risk as to the quality and performance of
255
   the Covered Software is with You. Should any Covered Software prove
256
   defective in any respect, You (not any Contributor) assume the cost of
257
   any necessary servicing, repair, or correction. This disclaimer of
258
   warranty constitutes an essential part of this License. No use of any
259
   Covered Software is authorized under this License except under this
260
   disclaimer.
261
262
7. Limitation of Liability
263
264
   Under no circumstances and under no legal theory, whether tort
265
   (including negligence), contract, or otherwise, shall any Contributor,
266
   or anyone who distributes Covered Software as permitted above, be
267
   liable to You for any direct, indirect, special, incidental, or
268
   consequential damages of any character including, without limitation,
269
   damages for lost profits, loss of goodwill, work stoppage, computer
270
   failure or malfunction, or any and all other commercial damages or
271
   losses, even if such party shall have been informed of the possibility
272
   of such damages. This limitation of liability shall not apply to
273
   liability for death or personal injury resulting from such party’s
274
   negligence to the extent applicable law prohibits such limitation. Some
275
   jurisdictions do not allow the exclusion or limitation of incidental or
276
   consequential damages, so this exclusion and limitation may not apply
277
   to You.
278
279
8. Litigation
280
281
   Any litigation relating to this License may be brought only in the
282
   courts of a jurisdiction where the defendant maintains its principal
283
   place of business and such litigation shall be governed by laws of that
284
   jurisdiction, without reference to its conflict-of-law provisions.
285
   Nothing in this Section shall prevent a party’s ability to bring
286
   cross-claims or counter-claims.
287
288
9. Miscellaneous
289
290
   This License represents the complete agreement concerning the subject
291
   matter hereof. If any provision of this License is held to be
292
   unenforceable, such provision shall be reformed only to the extent
293
   necessary to make it enforceable. Any law or regulation which provides
294
   that the language of a contract shall be construed against the drafter
295
   shall not be used to construe this License against a Contributor.
296
297
10. Versions of the License
298
299
  10.1. New Versions
300
301
   Mozilla Foundation is the license steward. Except as provided in
302
   Section 10.3, no one other than the license steward has the right to
303
   modify or publish new versions of this License. Each version will be
304
   given a distinguishing version number.
305
306
  10.2. Effect of New Versions
307
308
   You may distribute the Covered Software under the terms of the version
309
   of the License under which You originally received the Covered
310
   Software, or under the terms of any subsequent version published by the
311
   license steward.
312
313
  10.3. Modified Versions
314
315
   If you create software not governed by this License, and you want to
316
   create a new license for such software, you may create and use a
317
   modified version of this License if you rename the license and remove
318
   any references to the name of the license steward (except to note that
319
   such modified license differs from this License).
320
321
  10.4. Distributing Source Code Form that is Incompatible With Secondary
322
  Licenses
323
324
   If You choose to distribute Source Code Form that is Incompatible With
325
   Secondary Licenses under the terms of this version of the License, the
326
   notice described in Exhibit B of this License must be attached.
327
328
Exhibit A - Source Code Form License Notice
329
330
     This Source Code Form is subject to the terms of the Mozilla Public
331
     License, v. 2.0. If a copy of the MPL was not distributed with this
332
     file, You can obtain one at https://mozilla.org/MPL/2.0/.
333
334
   If it is not possible or desirable to put the notice in a particular
335
   file, then You may include the notice in a location (such as a LICENSE
336
   file in a relevant directory) where a recipient would be likely to look
337
   for such a notice.
338
339
   You may add additional accurate notices of copyright ownership.
340
341
Exhibit B - “Incompatible With Secondary Licenses” Notice
342
343
     This Source Code Form is “Incompatible With Secondary Licenses”, as
344
     defined by the Mozilla Public License, v. 2.0.
1345
A licenses/UNDO-FX.md
1
Copyright (c) 2014, TomasMikula
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without modification,
5
are permitted provided that the following conditions are met:
6
7
* Redistributions of source code must retain the above copyright notice, this
8
  list of conditions and the following disclaimer.
9
10
* Redistributions in binary form must reproduce the above copyright notice, this
11
  list of conditions and the following disclaimer in the documentation and/or
12
  other materials provided with the distribution.
113
14
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
A licenses/WELL-BEHAVED-FX.md
1
Copyright (c) 2014, TomasMikula
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are met:
6
7
* Redistributions of source code must retain the above copyright notice, this
8
  list of conditions and the following disclaimer.
9
10
* Redistributions in binary form must reproduce the above copyright notice,
11
  this list of conditions and the following disclaimer in the documentation
12
  and/or other materials provided with the distribution.
13
14
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
125
M src/main/java/com/scrivenvar/Constants.java
120120
121121
  /**
122
   * Default starting delimiter when inserting R variables.
123
   */
124
  public static final String R_DELIMITER_BEGAN_DEFAULT = "x( ";
125
126
  /**
127
   * Default ending delimiter when inserting R variables.
128
   */
129
  public static final String R_DELIMITER_ENDED_DEFAULT = " )";
130
131
  /**
122132
   * Resource directory where different language lexicons are located.
123133
   */
M src/main/java/com/scrivenvar/FileEditorTabPane.java
229229
230230
  /**
231
   * Called when the user selects New from the File menu.
231
   * Called to add a new {@link FileEditorTab} to the tab pane. The return
232
   * value is used when initializing the application, but isn't required.
233
   *
234
   * @return The new {@link FileEditorTab} instance created.
232235
   */
233
  void newEditor() {
236
  FileEditorTab newEditor() {
234237
    final FileEditorTab tab = createFileEditor( getDefaultPath() );
235238
236239
    getTabs().add( tab );
237240
    getSelectionModel().select( tab );
241
    return tab;
238242
  }
239243
M src/main/java/com/scrivenvar/Main.java
6868
  }
6969
70
  /**
71
   * Must be static, otherwise instant crash.
72
   */
7073
  private final static Notifier sNotifier = Services.load( Notifier.class );
7174
  private final Options mOptions = Services.load( Options.class );
7275
  private final Snitch mSnitch = Services.load( Snitch.class );
76
7377
  private final Thread mSnitchThread = new Thread( getSnitch() );
7478
  private final MainWindow mMainWindow = new MainWindow();
M src/main/java/com/scrivenvar/MainWindow.java
8282
import org.fxmisc.richtext.model.StyleSpansBuilder;
8383
import org.reactfx.value.Val;
84
import org.xhtmlrenderer.util.XRLog;
85
86
import java.io.BufferedReader;
87
import java.io.InputStreamReader;
88
import java.nio.file.Path;
89
import java.nio.file.Paths;
90
import java.util.*;
91
import java.util.concurrent.atomic.AtomicInteger;
92
import java.util.function.Function;
93
import java.util.prefs.Preferences;
94
import java.util.stream.Collectors;
95
96
import static com.scrivenvar.Constants.*;
97
import static com.scrivenvar.Messages.get;
98
import static com.scrivenvar.util.StageState.*;
99
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
100
import static java.nio.charset.StandardCharsets.UTF_8;
101
import static java.util.Collections.emptyList;
102
import static java.util.Collections.singleton;
103
import static javafx.application.Platform.runLater;
104
import static javafx.event.Event.fireEvent;
105
import static javafx.scene.input.KeyCode.ENTER;
106
import static javafx.scene.input.KeyCode.TAB;
107
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
108
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward;
109
110
/**
111
 * Main window containing a tab pane in the center for file editors.
112
 */
113
public class MainWindow implements Observer {
114
  /**
115
   * The {@code OPTIONS} variable must be declared before all other variables
116
   * to prevent subsequent initializations from failing due to missing user
117
   * preferences.
118
   */
119
  private final static Options OPTIONS = Services.load( Options.class );
120
  private final static Snitch SNITCH = Services.load( Snitch.class );
121
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
122
123
  private final Scene mScene;
124
  private final StatusBar mStatusBar;
125
  private final Text mLineNumberText;
126
  private final TextField mFindTextField;
127
  private final SpellChecker mSpellChecker;
128
129
  private final Object mMutex = new Object();
130
131
  /**
132
   * Prevents re-instantiation of processing classes.
133
   */
134
  private final Map<FileEditorTab, Processor<String>> mProcessors =
135
      new HashMap<>();
136
137
  private final Map<String, String> mResolvedMap =
138
      new HashMap<>( DEFAULT_MAP_SIZE );
139
140
  /**
141
   * Called when the definition data is changed.
142
   */
143
  private final EventHandler<TreeItem.TreeModificationEvent<Event>>
144
      mTreeHandler = event -> {
145
    exportDefinitions( getDefinitionPath() );
146
    interpolateResolvedMap();
147
    renderActiveTab();
148
  };
149
150
  /**
151
   * Called to switch to the definition pane when the user presses the TAB key.
152
   */
153
  private final EventHandler<? super KeyEvent> mTabKeyHandler =
154
      (EventHandler<KeyEvent>) event -> {
155
        if( event.getCode() == TAB ) {
156
          getDefinitionPane().requestFocus();
157
          event.consume();
158
        }
159
      };
160
161
  /**
162
   * Called to inject the selected item when the user presses ENTER in the
163
   * definition pane.
164
   */
165
  private final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
166
      event -> {
167
        if( event.getCode() == ENTER ) {
168
          getVariableNameInjector().injectSelectedItem();
169
        }
170
      };
171
172
  private final ChangeListener<Integer> mCaretPositionListener =
173
      ( observable, oldPosition, newPosition ) -> {
174
        final FileEditorTab tab = getActiveFileEditorTab();
175
        final EditorPane pane = tab.getEditorPane();
176
        final StyleClassedTextArea editor = pane.getEditor();
177
178
        getLineNumberText().setText(
179
            get( STATUS_BAR_LINE,
180
                 editor.getCurrentParagraph() + 1,
181
                 editor.getParagraphs().size(),
182
                 editor.getCaretPosition()
183
            )
184
        );
185
      };
186
187
  private final ChangeListener<Integer> mCaretParagraphListener =
188
      ( observable, oldIndex, newIndex ) ->
189
          scrollToParagraph( newIndex, true );
190
191
  private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
192
  private final DefinitionPane mDefinitionPane = new DefinitionPane();
193
  private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
194
  private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
195
      mCaretPositionListener,
196
      mCaretParagraphListener );
197
198
  /**
199
   * Listens on the definition pane for double-click events.
200
   */
201
  private final VariableNameInjector mVariableNameInjector
202
      = new VariableNameInjector( mDefinitionPane );
203
204
  public MainWindow() {
205
    mStatusBar = createStatusBar();
206
    mLineNumberText = createLineNumberText();
207
    mFindTextField = createFindTextField();
208
    mScene = createScene();
209
    mSpellChecker = createSpellChecker();
210
211
    System.getProperties()
212
          .setProperty( "xr.util-logging.loggingEnabled", "true" );
213
    XRLog.setLoggingEnabled( true );
214
215
    initLayout();
216
    initFindInput();
217
    initSnitch();
218
    initDefinitionListener();
219
    initTabAddedListener();
220
    initTabChangedListener();
221
    initPreferences();
222
    initVariableNameInjector();
223
224
    NOTIFIER.addObserver( this );
225
  }
226
227
  private void initLayout() {
228
    final Scene appScene = getScene();
229
230
    appScene.getStylesheets().add( STYLESHEET_SCENE );
231
232
    // TODO: Apply an XML syntax highlighting for XML files.
233
//    appScene.getStylesheets().add( STYLESHEET_XML );
234
    appScene.windowProperty().addListener(
235
        ( observable, oldWindow, newWindow ) ->
236
            newWindow.setOnCloseRequest(
237
                e -> {
238
                  if( !getFileEditorPane().closeAllEditors() ) {
239
                    e.consume();
240
                  }
241
                }
242
            )
243
    );
244
  }
245
246
  /**
247
   * Initialize the find input text field to listen on F3, ENTER, and
248
   * ESCAPE key presses.
249
   */
250
  private void initFindInput() {
251
    final TextField input = getFindTextField();
252
253
    input.setOnKeyPressed( ( KeyEvent event ) -> {
254
      switch( event.getCode() ) {
255
        case F3:
256
        case ENTER:
257
          editFindNext();
258
          break;
259
        case F:
260
          if( !event.isControlDown() ) {
261
            break;
262
          }
263
        case ESCAPE:
264
          getStatusBar().setGraphic( null );
265
          getActiveFileEditorTab().getEditorPane().requestFocus();
266
          break;
267
      }
268
    } );
269
270
    // Remove when the input field loses focus.
271
    input.focusedProperty().addListener(
272
        ( focused, oldFocus, newFocus ) -> {
273
          if( !newFocus ) {
274
            getStatusBar().setGraphic( null );
275
          }
276
        }
277
    );
278
  }
279
280
  /**
281
   * Watch for changes to external files. In particular, this awaits
282
   * modifications to any XSL files associated with XML files being edited.
283
   * When
284
   * an XSL file is modified (external to the application), the snitch's ears
285
   * perk up and the file is reloaded. This keeps the XSL transformation up to
286
   * date with what's on the file system.
287
   */
288
  private void initSnitch() {
289
    SNITCH.addObserver( this );
290
  }
291
292
  /**
293
   * Listen for {@link FileEditorTabPane} to receive open definition file
294
   * event.
295
   */
296
  private void initDefinitionListener() {
297
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
298
        ( final ObservableValue<? extends Path> file,
299
          final Path oldPath, final Path newPath ) -> {
300
          // Indirectly refresh the resolved map.
301
          resetProcessors();
302
303
          openDefinitions( newPath );
304
305
          // Will create new processors and therefore a new resolved map.
306
          renderActiveTab();
307
        }
308
    );
309
  }
310
311
  /**
312
   * When tabs are added, hook the various change listeners onto the new
313
   * tab sothat the preview pane refreshes as necessary.
314
   */
315
  private void initTabAddedListener() {
316
    final FileEditorTabPane editorPane = getFileEditorPane();
317
318
    // Make sure the text processor kicks off when new files are opened.
319
    final ObservableList<Tab> tabs = editorPane.getTabs();
320
321
    // Update the preview pane on tab changes.
322
    tabs.addListener(
323
        ( final Change<? extends Tab> change ) -> {
324
          while( change.next() ) {
325
            if( change.wasAdded() ) {
326
              // Multiple tabs can be added simultaneously.
327
              for( final Tab newTab : change.getAddedSubList() ) {
328
                final FileEditorTab tab = (FileEditorTab) newTab;
329
330
                initTextChangeListener( tab );
331
                initTabKeyEventListener( tab );
332
                initScrollEventListener( tab );
333
                initSpellCheckListener( tab );
334
//              initSyntaxListener( tab );
335
              }
336
            }
337
          }
338
        }
339
    );
340
  }
341
342
  private void initTextChangeListener( final FileEditorTab tab ) {
343
    tab.addTextChangeListener(
344
        ( editor, oldValue, newValue ) -> {
345
          process( tab );
346
          scrollToParagraph( getCurrentParagraphIndex() );
347
        }
348
    );
349
  }
350
351
  /**
352
   * Ensure that the keyboard events are received when a new tab is added
353
   * to the user interface.
354
   *
355
   * @param tab The tab editor that can trigger keyboard events.
356
   */
357
  private void initTabKeyEventListener( final FileEditorTab tab ) {
358
    tab.addEventFilter( KeyEvent.KEY_PRESSED, mTabKeyHandler );
359
  }
360
361
  private void initScrollEventListener( final FileEditorTab tab ) {
362
    final var scrollPane = tab.getScrollPane();
363
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
364
365
    // Before the drag handler can be attached, the scroll bar for the
366
    // text editor pane must be visible.
367
    final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
368
        runLater( () -> {
369
          if( newShow ) {
370
            final var handler = new ScrollEventHandler( scrollPane, scrollBar );
371
            handler.enabledProperty().bind( tab.selectedProperty() );
372
          }
373
        } );
374
375
    Val.flatMap( scrollPane.sceneProperty(), Scene::windowProperty )
376
       .flatMap( Window::showingProperty )
377
       .addListener( listener );
378
  }
379
380
  /**
381
   * Listen for changes to the any particular paragraph and perform a quick
382
   * spell check upon it. The style classes in the editor will be changed to
383
   * mark any spelling mistakes in the paragraph. The user may then interact
384
   * with any misspelled word (i.e., any piece of text that is marked) to
385
   * revise the spelling.
386
   *
387
   * @param tab The tab to spellcheck.
388
   */
389
  private void initSpellCheckListener( final FileEditorTab tab ) {
390
    final var editor = tab.getEditorPane().getEditor();
391
392
    // Use the plain text changes so that notifications of style changes
393
    // are suppressed.
394
    editor.plainTextChanges()
395
          .filter( p -> !p.isIdentity() ).subscribe( change -> {
396
397
      // Only perform a spell check on the current paragraph. The
398
      // entire document is processed once, when opened.
399
      final var offset = change.getPosition();
400
      final var position = editor.offsetToPosition( offset, Forward );
401
      final var paraId = position.getMajor();
402
      final var paragraph = editor.getParagraph( paraId );
403
      final var text = paragraph.getText();
404
405
      editor.clearStyle( paraId );
406
407
      final var builder = new StyleSpansBuilder<Collection<String>>();
408
      final var count = new AtomicInteger( 0 );
409
      final var runningIndex = new AtomicInteger( 0 );
410
411
      getSpellChecker().proofread( text, ( prevIndex, currIndex ) -> {
412
        builder.add( emptyList(), prevIndex - runningIndex.get() );
413
        builder.add( singleton( "spelling" ), currIndex - prevIndex );
414
        count.incrementAndGet();
415
        runningIndex.set( currIndex );
416
      } );
417
418
      if( count.get() > 0 ) {
419
        builder.add( emptyList(), text.length() - runningIndex.get() );
420
421
        final var spans = builder.create();
422
        editor.setStyleSpans( paraId, 0, spans );
423
      }
424
    } );
425
  }
426
427
  /**
428
   * Listen for new tab selection events.
429
   */
430
  private void initTabChangedListener() {
431
    final FileEditorTabPane editorPane = getFileEditorPane();
432
433
    // Update the preview pane changing tabs.
434
    editorPane.addTabSelectionListener(
435
        ( tabPane, oldTab, newTab ) -> {
436
          if( newTab == null ) {
437
            getPreviewPane().clear();
438
          }
439
440
          // If there was no old tab, then this is a first time load, which
441
          // can be ignored.
442
          if( oldTab != null ) {
443
            if( newTab != null ) {
444
              final FileEditorTab tab = (FileEditorTab) newTab;
445
              updateVariableNameInjector( tab );
446
              process( tab );
447
            }
448
          }
449
        }
450
    );
451
  }
452
453
  /**
454
   * Reloads the preferences from the previous session.
455
   */
456
  private void initPreferences() {
457
    initDefinitionPane();
458
    getFileEditorPane().initPreferences();
459
  }
460
461
  private void initVariableNameInjector() {
462
    updateVariableNameInjector( getActiveFileEditorTab() );
463
  }
464
465
  private int getCurrentParagraphIndex() {
466
    return getActiveEditorPane().getCurrentParagraphIndex();
467
  }
468
469
  private void scrollToParagraph( final int id ) {
470
    scrollToParagraph( id, false );
471
  }
472
473
  /**
474
   * @param id    The paragraph to scroll to, will be approximated if it doesn't
475
   *              exist.
476
   * @param force {@code true} means to force scrolling immediately, which
477
   *              should only be attempted when it is known that the document
478
   *              has been fully rendered. Otherwise the internal map of ID
479
   *              attributes will be incomplete and scrolling will flounder.
480
   */
481
  private void scrollToParagraph( final int id, final boolean force ) {
482
    synchronized( mMutex ) {
483
      final var previewPane = getPreviewPane();
484
      final var scrollPane = previewPane.getScrollPane();
485
      final int approxId = getActiveEditorPane().approximateParagraphId( id );
486
487
      if( force ) {
488
        previewPane.scrollTo( approxId );
489
      }
490
      else {
491
        previewPane.tryScrollTo( approxId );
492
      }
493
494
      scrollPane.repaint();
495
    }
496
  }
497
498
  private void updateVariableNameInjector( final FileEditorTab tab ) {
499
    getVariableNameInjector().addListener( tab );
500
  }
501
502
  /**
503
   * Called whenever the preview pane becomes out of sync with the file editor
504
   * tab. This can be called when the text changes, the caret paragraph
505
   * changes, or the file tab changes.
506
   *
507
   * @param tab The file editor tab that has been changed in some fashion.
508
   */
509
  private void process( final FileEditorTab tab ) {
510
    if( tab == null ) {
511
      return;
512
    }
513
514
    getPreviewPane().setPath( tab.getPath() );
515
516
    final Processor<String> processor = getProcessors().computeIfAbsent(
517
        tab, p -> createProcessors( tab )
518
    );
519
520
    try {
521
      processChain( processor, tab.getEditorText() );
522
    } catch( final Exception ex ) {
523
      error( ex );
524
    }
525
  }
526
527
  /**
528
   * Executes the processing chain, operating on the given string.
529
   *
530
   * @param handler The first processor in the chain to call.
531
   * @param text    The initial value of the text to process.
532
   * @return The final value of the text that was processed by the chain.
533
   */
534
  private String processChain( Processor<String> handler, String text ) {
535
    while( handler != null && text != null ) {
536
      text = handler.process( text );
537
      handler = handler.next();
538
    }
539
540
    return text;
541
  }
542
543
  private void renderActiveTab() {
544
    process( getActiveFileEditorTab() );
545
  }
546
547
  /**
548
   * Called when a definition source is opened.
549
   *
550
   * @param path Path to the definition source that was opened.
551
   */
552
  private void openDefinitions( final Path path ) {
553
    try {
554
      final DefinitionSource ds = createDefinitionSource( path );
555
      setDefinitionSource( ds );
556
      getUserPreferences().definitionPathProperty().setValue( path.toFile() );
557
      getUserPreferences().save();
558
559
      final Tooltip tooltipPath = new Tooltip( path.toString() );
560
      tooltipPath.setShowDelay( Duration.millis( 200 ) );
561
562
      final DefinitionPane pane = getDefinitionPane();
563
      pane.update( ds );
564
      pane.addTreeChangeHandler( mTreeHandler );
565
      pane.addKeyEventHandler( mDefinitionKeyHandler );
566
      pane.filenameProperty().setValue( path.getFileName().toString() );
567
      pane.setTooltip( tooltipPath );
568
569
      interpolateResolvedMap();
570
    } catch( final Exception e ) {
571
      error( e );
572
    }
573
  }
574
575
  private void exportDefinitions( final Path path ) {
576
    try {
577
      final DefinitionPane pane = getDefinitionPane();
578
      final TreeItem<String> root = pane.getTreeView().getRoot();
579
      final TreeItem<String> problemChild = pane.isTreeWellFormed();
580
581
      if( problemChild == null ) {
582
        getDefinitionSource().getTreeAdapter().export( root, path );
583
        getNotifier().clear();
584
      }
585
      else {
586
        final String msg = get(
587
            "yaml.error.tree.form", problemChild.getValue() );
588
        getNotifier().notify( msg );
589
      }
590
    } catch( final Exception e ) {
591
      error( e );
592
    }
593
  }
594
595
  private void interpolateResolvedMap() {
596
    final Map<String, String> treeMap = getDefinitionPane().toMap();
597
    final Map<String, String> map = new HashMap<>( treeMap );
598
    MapInterpolator.interpolate( map );
599
600
    getResolvedMap().clear();
601
    getResolvedMap().putAll( map );
602
  }
603
604
  private void initDefinitionPane() {
605
    openDefinitions( getDefinitionPath() );
606
  }
607
608
  /**
609
   * Called when an exception occurs that warrants the user's attention.
610
   *
611
   * @param e The exception with a message that the user should know about.
612
   */
613
  private void error( final Exception e ) {
614
    getNotifier().notify( e );
615
  }
616
617
  //---- File actions -------------------------------------------------------
618
619
  /**
620
   * Called when an {@link Observable} instance has changed. This is called
621
   * by both the {@link Snitch} service and the notify service. The @link
622
   * Snitch} service can be called for different file types, including
623
   * {@link DefinitionSource} instances.
624
   *
625
   * @param observable The observed instance.
626
   * @param value      The noteworthy item.
627
   */
628
  @Override
629
  public void update( final Observable observable, final Object value ) {
630
    if( value != null ) {
631
      if( observable instanceof Snitch && value instanceof Path ) {
632
        updateSelectedTab();
633
      }
634
      else if( observable instanceof Notifier && value instanceof String ) {
635
        updateStatusBar( (String) value );
636
      }
637
    }
638
  }
639
640
  /**
641
   * Updates the status bar to show the given message.
642
   *
643
   * @param s The message to show in the status bar.
644
   */
645
  private void updateStatusBar( final String s ) {
646
    runLater(
647
        () -> {
648
          final int index = s.indexOf( '\n' );
649
          final String message = s.substring(
650
              0, index > 0 ? index : s.length() );
651
652
          getStatusBar().setText( message );
653
        }
654
    );
655
  }
656
657
  /**
658
   * Called when a file has been modified.
659
   */
660
  private void updateSelectedTab() {
661
    runLater(
662
        () -> {
663
          // Brute-force XSLT file reload by re-instantiating all processors.
664
          resetProcessors();
665
          renderActiveTab();
666
        }
667
    );
668
  }
669
670
  /**
671
   * After resetting the processors, they will refresh anew to be up-to-date
672
   * with the files (text and definition) currently loaded into the editor.
673
   */
674
  private void resetProcessors() {
675
    getProcessors().clear();
676
  }
677
678
  //---- File actions -------------------------------------------------------
679
680
  private void fileNew() {
681
    getFileEditorPane().newEditor();
682
  }
683
684
  private void fileOpen() {
685
    getFileEditorPane().openFileDialog();
686
  }
687
688
  private void fileClose() {
689
    getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
690
  }
691
692
  /**
693
   * TODO: Upon closing, first remove the tab change listeners. (There's no
694
   * need to re-render each tab when all are being closed.)
695
   */
696
  private void fileCloseAll() {
697
    getFileEditorPane().closeAllEditors();
698
  }
699
700
  private void fileSave() {
701
    getFileEditorPane().saveEditor( getActiveFileEditorTab() );
702
  }
703
704
  private void fileSaveAs() {
705
    final FileEditorTab editor = getActiveFileEditorTab();
706
    getFileEditorPane().saveEditorAs( editor );
707
    getProcessors().remove( editor );
708
709
    try {
710
      process( editor );
711
    } catch( final Exception ex ) {
712
      getNotifier().notify( ex );
713
    }
714
  }
715
716
  private void fileSaveAll() {
717
    getFileEditorPane().saveAllEditors();
718
  }
719
720
  private void fileExit() {
721
    final Window window = getWindow();
722
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
723
  }
724
725
  //---- Edit actions -------------------------------------------------------
726
727
  /**
728
   * Transform the Markdown into HTML then copy that HTML into the copy
729
   * buffer.
730
   */
731
  private void copyHtml() {
732
    final var markdown = getActiveEditorPane().getText();
733
    final var processors = createProcessorFactory().createProcessors(
734
        getActiveFileEditorTab()
735
    );
736
737
    final var chain = processors.remove( HtmlPreviewProcessor.class );
738
739
    final String html = processChain( chain, markdown );
740
741
    final Clipboard clipboard = Clipboard.getSystemClipboard();
742
    final ClipboardContent content = new ClipboardContent();
743
    content.putString( html );
744
    clipboard.setContent( content );
745
  }
746
747
  /**
748
   * Used to find text in the active file editor window.
749
   */
750
  private void editFind() {
751
    final TextField input = getFindTextField();
752
    getStatusBar().setGraphic( input );
753
    input.requestFocus();
754
  }
755
756
  public void editFindNext() {
757
    getActiveFileEditorTab().searchNext( getFindTextField().getText() );
758
  }
759
760
  public void editPreferences() {
761
    getUserPreferences().show();
762
  }
763
764
  //---- Insert actions -----------------------------------------------------
765
766
  /**
767
   * Delegates to the active editor to handle wrapping the current text
768
   * selection with leading and trailing strings.
769
   *
770
   * @param leading  The string to put before the selection.
771
   * @param trailing The string to put after the selection.
772
   */
773
  private void insertMarkdown(
774
      final String leading, final String trailing ) {
775
    getActiveEditorPane().surroundSelection( leading, trailing );
776
  }
777
778
  private void insertMarkdown(
779
      final String leading, final String trailing, final String hint ) {
780
    getActiveEditorPane().surroundSelection( leading, trailing, hint );
781
  }
782
783
  //---- Help actions -------------------------------------------------------
784
785
  private void helpAbout() {
786
    final Alert alert = new Alert( AlertType.INFORMATION );
787
    alert.setTitle( get( "Dialog.about.title" ) );
788
    alert.setHeaderText( get( "Dialog.about.header" ) );
789
    alert.setContentText( get( "Dialog.about.content" ) );
790
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
791
    alert.initOwner( getWindow() );
792
793
    alert.showAndWait();
794
  }
795
796
  //---- Member creators ----------------------------------------------------
797
798
  private SpellChecker createSpellChecker() {
799
    try {
800
      final Collection<String> lexicon = readLexicon( "en.txt" );
801
      return SymSpellSpeller.forLexicon( lexicon );
802
    } catch( final Exception e ) {
803
      getNotifier().notify( e );
804
      return new PermissiveSpeller();
805
    }
806
  }
807
808
  /**
809
   * Factory to create processors that are suited to different file types.
810
   *
811
   * @param tab The tab that is subjected to processing.
812
   * @return A processor suited to the file type specified by the tab's path.
813
   */
814
  private Processor<String> createProcessors( final FileEditorTab tab ) {
815
    return createProcessorFactory().createProcessors( tab );
816
  }
817
818
  private ProcessorFactory createProcessorFactory() {
819
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
820
  }
821
822
  private HTMLPreviewPane createHTMLPreviewPane() {
823
    return new HTMLPreviewPane();
824
  }
825
826
  private DefinitionSource createDefaultDefinitionSource() {
827
    return new YamlDefinitionSource( getDefinitionPath() );
828
  }
829
830
  private DefinitionSource createDefinitionSource( final Path path ) {
831
    try {
832
      return createDefinitionFactory().createDefinitionSource( path );
833
    } catch( final Exception ex ) {
834
      error( ex );
835
      return createDefaultDefinitionSource();
836
    }
837
  }
838
839
  private TextField createFindTextField() {
840
    return new TextField();
841
  }
842
843
  private DefinitionFactory createDefinitionFactory() {
844
    return new DefinitionFactory();
845
  }
846
847
  private StatusBar createStatusBar() {
848
    return new StatusBar();
849
  }
850
851
  private Scene createScene() {
852
    final SplitPane splitPane = new SplitPane(
853
        getDefinitionPane().getNode(),
854
        getFileEditorPane().getNode(),
855
        getPreviewPane().getNode() );
856
857
    splitPane.setDividerPositions(
858
        getFloat( K_PANE_SPLIT_DEFINITION, .22f ),
859
        getFloat( K_PANE_SPLIT_EDITOR, .60f ),
860
        getFloat( K_PANE_SPLIT_PREVIEW, .18f ) );
861
862
    getDefinitionPane().prefHeightProperty()
863
                       .bind( splitPane.heightProperty() );
864
865
    final BorderPane borderPane = new BorderPane();
866
    borderPane.setPrefSize( 1280, 800 );
867
    borderPane.setTop( createMenuBar() );
868
    borderPane.setBottom( getStatusBar() );
869
    borderPane.setCenter( splitPane );
870
871
    final VBox statusBar = new VBox();
872
    statusBar.setAlignment( Pos.BASELINE_CENTER );
873
    statusBar.getChildren().add( getLineNumberText() );
874
    getStatusBar().getRightItems().add( statusBar );
875
876
    // Force preview pane refresh on Windows.
877
    if( SystemUtils.IS_OS_WINDOWS ) {
878
      splitPane.getDividers().get( 1 ).positionProperty().addListener(
879
          ( l, oValue, nValue ) -> runLater(
880
              () -> getPreviewPane().getScrollPane().repaint()
881
          )
882
      );
883
    }
884
885
    return new Scene( borderPane );
886
  }
887
888
  private Text createLineNumberText() {
889
    return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
890
  }
891
892
  private Node createMenuBar() {
893
    final BooleanBinding activeFileEditorIsNull =
894
        getFileEditorPane().activeFileEditorProperty().isNull();
895
896
    // File actions
897
    final Action fileNewAction = new ActionBuilder()
898
        .setText( "Main.menu.file.new" )
899
        .setAccelerator( "Shortcut+N" )
900
        .setIcon( FILE_ALT )
901
        .setAction( e -> fileNew() )
902
        .build();
903
    final Action fileOpenAction = new ActionBuilder()
904
        .setText( "Main.menu.file.open" )
905
        .setAccelerator( "Shortcut+O" )
906
        .setIcon( FOLDER_OPEN_ALT )
907
        .setAction( e -> fileOpen() )
908
        .build();
909
    final Action fileCloseAction = new ActionBuilder()
910
        .setText( "Main.menu.file.close" )
911
        .setAccelerator( "Shortcut+W" )
912
        .setAction( e -> fileClose() )
913
        .setDisable( activeFileEditorIsNull )
914
        .build();
915
    final Action fileCloseAllAction = new ActionBuilder()
916
        .setText( "Main.menu.file.close_all" )
917
        .setAction( e -> fileCloseAll() )
918
        .setDisable( activeFileEditorIsNull )
919
        .build();
920
    final Action fileSaveAction = new ActionBuilder()
921
        .setText( "Main.menu.file.save" )
922
        .setAccelerator( "Shortcut+S" )
923
        .setIcon( FLOPPY_ALT )
924
        .setAction( e -> fileSave() )
925
        .setDisable( createActiveBooleanProperty(
926
            FileEditorTab::modifiedProperty ).not() )
927
        .build();
928
    final Action fileSaveAsAction = new ActionBuilder()
929
        .setText( "Main.menu.file.save_as" )
930
        .setAction( e -> fileSaveAs() )
931
        .setDisable( activeFileEditorIsNull )
932
        .build();
933
    final Action fileSaveAllAction = new ActionBuilder()
934
        .setText( "Main.menu.file.save_all" )
935
        .setAccelerator( "Shortcut+Shift+S" )
936
        .setAction( e -> fileSaveAll() )
937
        .setDisable( Bindings.not(
938
            getFileEditorPane().anyFileEditorModifiedProperty() ) )
939
        .build();
940
    final Action fileExitAction = new ActionBuilder()
941
        .setText( "Main.menu.file.exit" )
942
        .setAction( e -> fileExit() )
943
        .build();
944
945
    // Edit actions
946
    final Action editCopyHtmlAction = new ActionBuilder()
947
        .setText( Messages.get( "Main.menu.edit.copy.html" ) )
948
        .setIcon( HTML5 )
949
        .setAction( e -> copyHtml() )
950
        .setDisable( activeFileEditorIsNull )
951
        .build();
952
953
    final Action editUndoAction = new ActionBuilder()
954
        .setText( "Main.menu.edit.undo" )
955
        .setAccelerator( "Shortcut+Z" )
956
        .setIcon( UNDO )
957
        .setAction( e -> getActiveEditorPane().undo() )
958
        .setDisable( createActiveBooleanProperty(
959
            FileEditorTab::canUndoProperty ).not() )
960
        .build();
961
    final Action editRedoAction = new ActionBuilder()
962
        .setText( "Main.menu.edit.redo" )
963
        .setAccelerator( "Shortcut+Y" )
964
        .setIcon( REPEAT )
965
        .setAction( e -> getActiveEditorPane().redo() )
966
        .setDisable( createActiveBooleanProperty(
967
            FileEditorTab::canRedoProperty ).not() )
968
        .build();
969
970
    final Action editCutAction = new ActionBuilder()
971
        .setText( Messages.get( "Main.menu.edit.cut" ) )
972
        .setAccelerator( "Shortcut+X" )
973
        .setIcon( CUT )
974
        .setAction( e -> getActiveEditorPane().cut() )
975
        .setDisable( activeFileEditorIsNull )
976
        .build();
977
    final Action editCopyAction = new ActionBuilder()
978
        .setText( Messages.get( "Main.menu.edit.copy" ) )
979
        .setAccelerator( "Shortcut+C" )
980
        .setIcon( COPY )
981
        .setAction( e -> getActiveEditorPane().copy() )
982
        .setDisable( activeFileEditorIsNull )
983
        .build();
984
    final Action editPasteAction = new ActionBuilder()
985
        .setText( Messages.get( "Main.menu.edit.paste" ) )
986
        .setAccelerator( "Shortcut+V" )
987
        .setIcon( PASTE )
988
        .setAction( e -> getActiveEditorPane().paste() )
989
        .setDisable( activeFileEditorIsNull )
990
        .build();
991
    final Action editSelectAllAction = new ActionBuilder()
992
        .setText( Messages.get( "Main.menu.edit.selectAll" ) )
993
        .setAccelerator( "Shortcut+A" )
994
        .setAction( e -> getActiveEditorPane().selectAll() )
995
        .setDisable( activeFileEditorIsNull )
996
        .build();
997
998
    final Action editFindAction = new ActionBuilder()
999
        .setText( "Main.menu.edit.find" )
1000
        .setAccelerator( "Ctrl+F" )
1001
        .setIcon( SEARCH )
1002
        .setAction( e -> editFind() )
1003
        .setDisable( activeFileEditorIsNull )
1004
        .build();
1005
    final Action editFindNextAction = new ActionBuilder()
1006
        .setText( "Main.menu.edit.find.next" )
1007
        .setAccelerator( "F3" )
1008
        .setIcon( null )
1009
        .setAction( e -> editFindNext() )
1010
        .setDisable( activeFileEditorIsNull )
1011
        .build();
1012
    final Action editPreferencesAction = new ActionBuilder()
1013
        .setText( "Main.menu.edit.preferences" )
1014
        .setAccelerator( "Ctrl+Alt+S" )
1015
        .setAction( e -> editPreferences() )
1016
        .build();
1017
1018
    // Insert actions
1019
    final Action insertBoldAction = new ActionBuilder()
1020
        .setText( "Main.menu.insert.bold" )
1021
        .setAccelerator( "Shortcut+B" )
1022
        .setIcon( BOLD )
1023
        .setAction( e -> insertMarkdown( "**", "**" ) )
1024
        .setDisable( activeFileEditorIsNull )
1025
        .build();
1026
    final Action insertItalicAction = new ActionBuilder()
1027
        .setText( "Main.menu.insert.italic" )
1028
        .setAccelerator( "Shortcut+I" )
1029
        .setIcon( ITALIC )
1030
        .setAction( e -> insertMarkdown( "*", "*" ) )
1031
        .setDisable( activeFileEditorIsNull )
1032
        .build();
1033
    final Action insertSuperscriptAction = new ActionBuilder()
1034
        .setText( "Main.menu.insert.superscript" )
1035
        .setAccelerator( "Shortcut+[" )
1036
        .setIcon( SUPERSCRIPT )
1037
        .setAction( e -> insertMarkdown( "^", "^" ) )
1038
        .setDisable( activeFileEditorIsNull )
1039
        .build();
1040
    final Action insertSubscriptAction = new ActionBuilder()
1041
        .setText( "Main.menu.insert.subscript" )
1042
        .setAccelerator( "Shortcut+]" )
1043
        .setIcon( SUBSCRIPT )
1044
        .setAction( e -> insertMarkdown( "~", "~" ) )
1045
        .setDisable( activeFileEditorIsNull )
1046
        .build();
1047
    final Action insertStrikethroughAction = new ActionBuilder()
1048
        .setText( "Main.menu.insert.strikethrough" )
1049
        .setAccelerator( "Shortcut+T" )
1050
        .setIcon( STRIKETHROUGH )
1051
        .setAction( e -> insertMarkdown( "~~", "~~" ) )
1052
        .setDisable( activeFileEditorIsNull )
1053
        .build();
1054
    final Action insertBlockquoteAction = new ActionBuilder()
1055
        .setText( "Main.menu.insert.blockquote" )
1056
        .setAccelerator( "Ctrl+Q" )
1057
        .setIcon( QUOTE_LEFT )
1058
        .setAction( e -> insertMarkdown( "\n\n> ", "" ) )
1059
        .setDisable( activeFileEditorIsNull )
1060
        .build();
1061
    final Action insertCodeAction = new ActionBuilder()
1062
        .setText( "Main.menu.insert.code" )
1063
        .setAccelerator( "Shortcut+K" )
1064
        .setIcon( CODE )
1065
        .setAction( e -> insertMarkdown( "`", "`" ) )
1066
        .setDisable( activeFileEditorIsNull )
1067
        .build();
1068
    final Action insertFencedCodeBlockAction = new ActionBuilder()
1069
        .setText( "Main.menu.insert.fenced_code_block" )
1070
        .setAccelerator( "Shortcut+Shift+K" )
1071
        .setIcon( FILE_CODE_ALT )
1072
        .setAction( e -> insertMarkdown(
1073
            "\n\n```\n",
1074
            "\n```\n\n",
1075
            get( "Main.menu.insert.fenced_code_block.prompt" ) ) )
1076
        .setDisable( activeFileEditorIsNull )
1077
        .build();
1078
    final Action insertLinkAction = new ActionBuilder()
1079
        .setText( "Main.menu.insert.link" )
1080
        .setAccelerator( "Shortcut+L" )
1081
        .setIcon( LINK )
1082
        .setAction( e -> getActiveEditorPane().insertLink() )
1083
        .setDisable( activeFileEditorIsNull )
1084
        .build();
1085
    final Action insertImageAction = new ActionBuilder()
1086
        .setText( "Main.menu.insert.image" )
1087
        .setAccelerator( "Shortcut+G" )
1088
        .setIcon( PICTURE_ALT )
1089
        .setAction( e -> getActiveEditorPane().insertImage() )
1090
        .setDisable( activeFileEditorIsNull )
1091
        .build();
1092
1093
    // Number of header actions (H1 ... H3)
1094
    final int HEADERS = 3;
1095
    final Action[] headers = new Action[ HEADERS ];
1096
1097
    for( int i = 1; i <= HEADERS; i++ ) {
1098
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
1099
      final String markup = String.format( "%n%n%s ", hashes );
1100
      final String text = "Main.menu.insert.header." + i;
1101
      final String accelerator = "Shortcut+" + i;
1102
      final String prompt = text + ".prompt";
1103
1104
      headers[ i - 1 ] = new ActionBuilder()
1105
          .setText( text )
1106
          .setAccelerator( accelerator )
1107
          .setIcon( HEADER )
1108
          .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) )
1109
          .setDisable( activeFileEditorIsNull )
1110
          .build();
1111
    }
1112
1113
    final Action insertUnorderedListAction = new ActionBuilder()
1114
        .setText( "Main.menu.insert.unordered_list" )
1115
        .setAccelerator( "Shortcut+U" )
1116
        .setIcon( LIST_UL )
1117
        .setAction( e -> insertMarkdown( "\n\n* ", "" ) )
1118
        .setDisable( activeFileEditorIsNull )
1119
        .build();
1120
    final Action insertOrderedListAction = new ActionBuilder()
1121
        .setText( "Main.menu.insert.ordered_list" )
1122
        .setAccelerator( "Shortcut+Shift+O" )
1123
        .setIcon( LIST_OL )
1124
        .setAction( e -> insertMarkdown(
1125
            "\n\n1. ", "" ) )
1126
        .setDisable( activeFileEditorIsNull )
1127
        .build();
1128
    final Action insertHorizontalRuleAction = new ActionBuilder()
1129
        .setText( "Main.menu.insert.horizontal_rule" )
1130
        .setAccelerator( "Shortcut+H" )
1131
        .setAction( e -> insertMarkdown(
1132
            "\n\n---\n\n", "" ) )
1133
        .setDisable( activeFileEditorIsNull )
1134
        .build();
1135
1136
    // Help actions
1137
    final Action helpAboutAction = new ActionBuilder()
1138
        .setText( "Main.menu.help.about" )
1139
        .setAction( e -> helpAbout() )
1140
        .build();
1141
1142
    //---- MenuBar ----
1143
    final Menu fileMenu = ActionUtils.createMenu(
1144
        get( "Main.menu.file" ),
1145
        fileNewAction,
1146
        fileOpenAction,
1147
        null,
1148
        fileCloseAction,
1149
        fileCloseAllAction,
1150
        null,
1151
        fileSaveAction,
1152
        fileSaveAsAction,
1153
        fileSaveAllAction,
1154
        null,
1155
        fileExitAction );
1156
1157
    final Menu editMenu = ActionUtils.createMenu(
1158
        get( "Main.menu.edit" ),
1159
        editCopyHtmlAction,
1160
        null,
1161
        editUndoAction,
1162
        editRedoAction,
1163
        null,
1164
        editCutAction,
1165
        editCopyAction,
1166
        editPasteAction,
1167
        editSelectAllAction,
1168
        null,
1169
        editFindAction,
1170
        editFindNextAction,
1171
        null,
1172
        editPreferencesAction );
1173
1174
    final Menu insertMenu = ActionUtils.createMenu(
1175
        get( "Main.menu.insert" ),
1176
        insertBoldAction,
1177
        insertItalicAction,
1178
        insertSuperscriptAction,
1179
        insertSubscriptAction,
1180
        insertStrikethroughAction,
1181
        insertBlockquoteAction,
1182
        insertCodeAction,
1183
        insertFencedCodeBlockAction,
1184
        null,
1185
        insertLinkAction,
1186
        insertImageAction,
1187
        null,
1188
        headers[ 0 ],
1189
        headers[ 1 ],
1190
        headers[ 2 ],
1191
        null,
1192
        insertUnorderedListAction,
1193
        insertOrderedListAction,
1194
        insertHorizontalRuleAction
1195
    );
1196
1197
    final Menu helpMenu = ActionUtils.createMenu(
1198
        get( "Main.menu.help" ),
1199
        helpAboutAction );
1200
1201
    final MenuBar menuBar = new MenuBar(
1202
        fileMenu,
1203
        editMenu,
1204
        insertMenu,
1205
        helpMenu );
1206
1207
    //---- ToolBar ----
1208
    final ToolBar toolBar = ActionUtils.createToolBar(
1209
        fileNewAction,
1210
        fileOpenAction,
1211
        fileSaveAction,
1212
        null,
1213
        editUndoAction,
1214
        editRedoAction,
1215
        editCutAction,
1216
        editCopyAction,
1217
        editPasteAction,
1218
        null,
1219
        insertBoldAction,
1220
        insertItalicAction,
1221
        insertSuperscriptAction,
1222
        insertSubscriptAction,
1223
        insertBlockquoteAction,
1224
        insertCodeAction,
1225
        insertFencedCodeBlockAction,
1226
        null,
1227
        insertLinkAction,
1228
        insertImageAction,
1229
        null,
1230
        headers[ 0 ],
1231
        null,
1232
        insertUnorderedListAction,
1233
        insertOrderedListAction );
1234
1235
    return new VBox( menuBar, toolBar );
1236
  }
1237
1238
  /**
1239
   * Creates a boolean property that is bound to another boolean value of the
1240
   * active editor.
1241
   */
1242
  private BooleanProperty createActiveBooleanProperty(
1243
      final Function<FileEditorTab, ObservableBooleanValue> func ) {
1244
1245
    final BooleanProperty b = new SimpleBooleanProperty();
1246
    final FileEditorTab tab = getActiveFileEditorTab();
1247
1248
    if( tab != null ) {
1249
      b.bind( func.apply( tab ) );
1250
    }
1251
1252
    getFileEditorPane().activeFileEditorProperty().addListener(
1253
        ( observable, oldFileEditor, newFileEditor ) -> {
1254
          b.unbind();
1255
1256
          if( newFileEditor == null ) {
1257
            b.set( false );
1258
          }
1259
          else {
1260
            b.bind( func.apply( newFileEditor ) );
1261
          }
1262
        }
1263
    );
1264
1265
    return b;
1266
  }
1267
1268
  //---- Convenience accessors ----------------------------------------------
1269
1270
  private Preferences getPreferences() {
1271
    return OPTIONS.getState();
1272
  }
1273
1274
  private float getFloat( final String key, final float defaultValue ) {
1275
    return getPreferences().getFloat( key, defaultValue );
1276
  }
1277
1278
  public Window getWindow() {
1279
    return getScene().getWindow();
1280
  }
1281
1282
  private MarkdownEditorPane getActiveEditorPane() {
1283
    return getActiveFileEditorTab().getEditorPane();
1284
  }
1285
1286
  private FileEditorTab getActiveFileEditorTab() {
1287
    return getFileEditorPane().getActiveFileEditor();
1288
  }
1289
1290
  //---- Member accessors ---------------------------------------------------
1291
1292
  protected Scene getScene() {
1293
    return mScene;
1294
  }
1295
1296
  private SpellChecker getSpellChecker() {
1297
    return mSpellChecker;
1298
  }
1299
1300
  private Map<FileEditorTab, Processor<String>> getProcessors() {
1301
    return mProcessors;
1302
  }
1303
1304
  private FileEditorTabPane getFileEditorPane() {
1305
    return mFileEditorPane;
1306
  }
1307
1308
  private HTMLPreviewPane getPreviewPane() {
1309
    return mPreviewPane;
1310
  }
1311
1312
  private void setDefinitionSource(
1313
      final DefinitionSource definitionSource ) {
1314
    assert definitionSource != null;
1315
    mDefinitionSource = definitionSource;
1316
  }
1317
1318
  private DefinitionSource getDefinitionSource() {
1319
    return mDefinitionSource;
1320
  }
1321
1322
  private DefinitionPane getDefinitionPane() {
1323
    return mDefinitionPane;
1324
  }
1325
1326
  private Text getLineNumberText() {
1327
    return mLineNumberText;
1328
  }
1329
1330
  private StatusBar getStatusBar() {
1331
    return mStatusBar;
1332
  }
1333
1334
  private TextField getFindTextField() {
1335
    return mFindTextField;
1336
  }
1337
1338
  private VariableNameInjector getVariableNameInjector() {
1339
    return mVariableNameInjector;
1340
  }
1341
1342
  /**
1343
   * Returns the variable map of interpolated definitions.
1344
   *
1345
   * @return A map to help dereference variables.
1346
   */
1347
  private Map<String, String> getResolvedMap() {
1348
    return mResolvedMap;
1349
  }
1350
1351
  private Notifier getNotifier() {
1352
    return NOTIFIER;
1353
  }
1354
1355
  //---- Persistence accessors ----------------------------------------------
1356
1357
  private UserPreferences getUserPreferences() {
1358
    return OPTIONS.getUserPreferences();
1359
  }
1360
1361
  private Path getDefinitionPath() {
1362
    return getUserPreferences().getDefinitionPath();
1363
  }
1364
1365
  //---- Resource accessors -------------------------------------------------
84
85
import java.io.BufferedReader;
86
import java.io.InputStreamReader;
87
import java.nio.file.Path;
88
import java.nio.file.Paths;
89
import java.util.*;
90
import java.util.concurrent.atomic.AtomicInteger;
91
import java.util.function.Consumer;
92
import java.util.function.Function;
93
import java.util.prefs.Preferences;
94
import java.util.stream.Collectors;
95
96
import static com.scrivenvar.Constants.*;
97
import static com.scrivenvar.Messages.get;
98
import static com.scrivenvar.util.StageState.*;
99
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
100
import static java.nio.charset.StandardCharsets.UTF_8;
101
import static java.util.Collections.emptyList;
102
import static java.util.Collections.singleton;
103
import static javafx.application.Platform.runLater;
104
import static javafx.event.Event.fireEvent;
105
import static javafx.scene.input.KeyCode.ENTER;
106
import static javafx.scene.input.KeyCode.TAB;
107
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
108
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward;
109
110
/**
111
 * Main window containing a tab pane in the center for file editors.
112
 */
113
public class MainWindow implements Observer {
114
  /**
115
   * The {@code OPTIONS} variable must be declared before all other variables
116
   * to prevent subsequent initializations from failing due to missing user
117
   * preferences.
118
   */
119
  private final static Options sOptions = Services.load( Options.class );
120
  private final static Snitch SNITCH = Services.load( Snitch.class );
121
  private final static Notifier sNotifier = Services.load( Notifier.class );
122
123
  private final Scene mScene;
124
  private final StatusBar mStatusBar;
125
  private final Text mLineNumberText;
126
  private final TextField mFindTextField;
127
  private final SpellChecker mSpellChecker;
128
129
  private final Object mMutex = new Object();
130
131
  /**
132
   * Prevents re-instantiation of processing classes.
133
   */
134
  private final Map<FileEditorTab, Processor<String>> mProcessors =
135
      new HashMap<>();
136
137
  private final Map<String, String> mResolvedMap =
138
      new HashMap<>( DEFAULT_MAP_SIZE );
139
140
  /**
141
   * Called when the definition data is changed.
142
   */
143
  private final EventHandler<TreeItem.TreeModificationEvent<Event>>
144
      mTreeHandler = event -> {
145
    exportDefinitions( getDefinitionPath() );
146
    interpolateResolvedMap();
147
    renderActiveTab();
148
  };
149
150
  /**
151
   * Called to switch to the definition pane when the user presses the TAB key.
152
   */
153
  private final EventHandler<? super KeyEvent> mTabKeyHandler =
154
      (EventHandler<KeyEvent>) event -> {
155
        if( event.getCode() == TAB ) {
156
          getDefinitionPane().requestFocus();
157
          event.consume();
158
        }
159
      };
160
161
  /**
162
   * Called to inject the selected item when the user presses ENTER in the
163
   * definition pane.
164
   */
165
  private final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
166
      event -> {
167
        if( event.getCode() == ENTER ) {
168
          getVariableNameInjector().injectSelectedItem();
169
        }
170
      };
171
172
  private final ChangeListener<Integer> mCaretPositionListener =
173
      ( observable, oldPosition, newPosition ) -> {
174
        final FileEditorTab tab = getActiveFileEditorTab();
175
        final EditorPane pane = tab.getEditorPane();
176
        final StyleClassedTextArea editor = pane.getEditor();
177
178
        getLineNumberText().setText(
179
            get( STATUS_BAR_LINE,
180
                 editor.getCurrentParagraph() + 1,
181
                 editor.getParagraphs().size(),
182
                 editor.getCaretPosition()
183
            )
184
        );
185
      };
186
187
  private final ChangeListener<Integer> mCaretParagraphListener =
188
      ( observable, oldIndex, newIndex ) ->
189
          scrollToParagraph( newIndex, true );
190
191
  private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
192
  private final DefinitionPane mDefinitionPane = new DefinitionPane();
193
  private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
194
  private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
195
      mCaretPositionListener,
196
      mCaretParagraphListener );
197
198
  /**
199
   * Listens on the definition pane for double-click events.
200
   */
201
  private final VariableNameInjector mVariableNameInjector
202
      = new VariableNameInjector( mDefinitionPane );
203
204
  public MainWindow() {
205
    mStatusBar = createStatusBar();
206
    mLineNumberText = createLineNumberText();
207
    mFindTextField = createFindTextField();
208
    mScene = createScene();
209
    mSpellChecker = createSpellChecker();
210
211
    initLayout();
212
    initFindInput();
213
    initSnitch();
214
    initDefinitionListener();
215
    initTabAddedListener();
216
    initTabChangedListener();
217
    initPreferences();
218
    initVariableNameInjector();
219
220
    sNotifier.addObserver( this );
221
  }
222
223
  private void initLayout() {
224
    final Scene appScene = getScene();
225
226
    appScene.getStylesheets().add( STYLESHEET_SCENE );
227
228
    // TODO: Apply an XML syntax highlighting for XML files.
229
//    appScene.getStylesheets().add( STYLESHEET_XML );
230
    appScene.windowProperty().addListener(
231
        ( observable, oldWindow, newWindow ) ->
232
            newWindow.setOnCloseRequest(
233
                e -> {
234
                  if( !getFileEditorPane().closeAllEditors() ) {
235
                    e.consume();
236
                  }
237
                }
238
            )
239
    );
240
  }
241
242
  /**
243
   * Initialize the find input text field to listen on F3, ENTER, and
244
   * ESCAPE key presses.
245
   */
246
  private void initFindInput() {
247
    final TextField input = getFindTextField();
248
249
    input.setOnKeyPressed( ( KeyEvent event ) -> {
250
      switch( event.getCode() ) {
251
        case F3:
252
        case ENTER:
253
          editFindNext();
254
          break;
255
        case F:
256
          if( !event.isControlDown() ) {
257
            break;
258
          }
259
        case ESCAPE:
260
          getStatusBar().setGraphic( null );
261
          getActiveFileEditorTab().getEditorPane().requestFocus();
262
          break;
263
      }
264
    } );
265
266
    // Remove when the input field loses focus.
267
    input.focusedProperty().addListener(
268
        ( focused, oldFocus, newFocus ) -> {
269
          if( !newFocus ) {
270
            getStatusBar().setGraphic( null );
271
          }
272
        }
273
    );
274
  }
275
276
  /**
277
   * Watch for changes to external files. In particular, this awaits
278
   * modifications to any XSL files associated with XML files being edited.
279
   * When
280
   * an XSL file is modified (external to the application), the snitch's ears
281
   * perk up and the file is reloaded. This keeps the XSL transformation up to
282
   * date with what's on the file system.
283
   */
284
  private void initSnitch() {
285
    SNITCH.addObserver( this );
286
  }
287
288
  /**
289
   * Listen for {@link FileEditorTabPane} to receive open definition file
290
   * event.
291
   */
292
  private void initDefinitionListener() {
293
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
294
        ( final ObservableValue<? extends Path> file,
295
          final Path oldPath, final Path newPath ) -> {
296
          // Indirectly refresh the resolved map.
297
          resetProcessors();
298
299
          openDefinitions( newPath );
300
301
          // Will create new processors and therefore a new resolved map.
302
          renderActiveTab();
303
        }
304
    );
305
  }
306
307
  /**
308
   * When tabs are added, hook the various change listeners onto the new
309
   * tab sothat the preview pane refreshes as necessary.
310
   */
311
  private void initTabAddedListener() {
312
    final FileEditorTabPane editorPane = getFileEditorPane();
313
314
    // Make sure the text processor kicks off when new files are opened.
315
    final ObservableList<Tab> tabs = editorPane.getTabs();
316
317
    // Update the preview pane on tab changes.
318
    tabs.addListener(
319
        ( final Change<? extends Tab> change ) -> {
320
          while( change.next() ) {
321
            if( change.wasAdded() ) {
322
              // Multiple tabs can be added simultaneously.
323
              for( final Tab newTab : change.getAddedSubList() ) {
324
                final FileEditorTab tab = (FileEditorTab) newTab;
325
326
                initTextChangeListener( tab );
327
                initTabKeyEventListener( tab );
328
                initScrollEventListener( tab );
329
                initSpellCheckListener( tab );
330
//              initSyntaxListener( tab );
331
              }
332
            }
333
          }
334
        }
335
    );
336
  }
337
338
  private void initTextChangeListener( final FileEditorTab tab ) {
339
    tab.addTextChangeListener(
340
        ( editor, oldValue, newValue ) -> {
341
          process( tab );
342
          scrollToParagraph( getCurrentParagraphIndex() );
343
        }
344
    );
345
  }
346
347
  /**
348
   * Ensure that the keyboard events are received when a new tab is added
349
   * to the user interface.
350
   *
351
   * @param tab The tab editor that can trigger keyboard events.
352
   */
353
  private void initTabKeyEventListener( final FileEditorTab tab ) {
354
    tab.addEventFilter( KeyEvent.KEY_PRESSED, mTabKeyHandler );
355
  }
356
357
  private void initScrollEventListener( final FileEditorTab tab ) {
358
    final var scrollPane = tab.getScrollPane();
359
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
360
361
    addShowListener( scrollPane, ( __ ) -> {
362
      final var handler = new ScrollEventHandler( scrollPane, scrollBar );
363
      handler.enabledProperty().bind( tab.selectedProperty() );
364
    } );
365
  }
366
367
  /**
368
   * Listen for changes to the any particular paragraph and perform a quick
369
   * spell check upon it. The style classes in the editor will be changed to
370
   * mark any spelling mistakes in the paragraph. The user may then interact
371
   * with any misspelled word (i.e., any piece of text that is marked) to
372
   * revise the spelling.
373
   *
374
   * @param tab The tab to spellcheck.
375
   */
376
  private void initSpellCheckListener( final FileEditorTab tab ) {
377
    final var editor = tab.getEditorPane().getEditor();
378
379
    addShowListener(
380
        editor, ( __ ) -> spellcheck( editor, editor.getText() )
381
    );
382
383
    // Use the plain text changes so that notifications of style changes
384
    // are suppressed. Checking against the identity ensures that only
385
    // new text additions or deletions trigger proofreading.
386
    editor.plainTextChanges()
387
          .filter( p -> !p.isIdentity() ).subscribe( change -> {
388
389
      // Only perform a spell check on the current paragraph. The
390
      // entire document is processed once, when opened.
391
      final var offset = change.getPosition();
392
      final var position = editor.offsetToPosition( offset, Forward );
393
      final var paraId = position.getMajor();
394
      final var paragraph = editor.getParagraph( paraId );
395
      final var text = paragraph.getText();
396
397
      // Ensure that styles aren't doubled-up.
398
      editor.clearStyle( paraId );
399
400
      spellcheck( editor, text, paraId );
401
    } );
402
  }
403
404
  /**
405
   * Listen for new tab selection events.
406
   */
407
  private void initTabChangedListener() {
408
    final FileEditorTabPane editorPane = getFileEditorPane();
409
410
    // Update the preview pane changing tabs.
411
    editorPane.addTabSelectionListener(
412
        ( tabPane, oldTab, newTab ) -> {
413
          // Clear the preview pane when closing an editor. When the last
414
          // tab is closed, this ensures that the preview pane is empty.
415
          if( newTab == null ) {
416
            getPreviewPane().clear();
417
          }
418
419
          // If there was no old tab, then this is a first time load, which
420
          // can be ignored.
421
          if( oldTab != null ) {
422
            if( newTab != null ) {
423
              final FileEditorTab tab = (FileEditorTab) newTab;
424
              updateVariableNameInjector( tab );
425
              process( tab );
426
            }
427
          }
428
        }
429
    );
430
  }
431
432
  /**
433
   * Reloads the preferences from the previous session.
434
   */
435
  private void initPreferences() {
436
    initDefinitionPane();
437
    final var editor = getFileEditorPane();
438
    editor.initPreferences();
439
    final var tab = editor.newEditor();
440
441
    // This is a bonafide hack to ensure the preview panel scales any images
442
    // to fit the panel width. The preview panel width isn't known until after
443
    // the main window is displayed. However, these preferences are initialized
444
    // prior to showing the main window. The preferences include loading the
445
    // text for an editor, which then parses it. Upon parsing, the width of
446
    // the preview pane is a negative (invalid) value. By waiting to load the
447
    // editors until after the main window is shown, a valid preview panel
448
    // width can be determined and thus the images scaled to fit.
449
    //
450
    // To avoid this hack, the preferences need to be loaded separately from
451
    // opening the editors. Those preferences can be used to get the window
452
    // sizes for showing the main window. Once the main window is shown, all
453
    // the subsequent initializations can take place.
454
    addShowListener( editor, ( __ ) -> {
455
      editor.closeEditor( tab, false );
456
      editor.initPreferences();
457
    } );
458
  }
459
460
  private void initVariableNameInjector() {
461
    updateVariableNameInjector( getActiveFileEditorTab() );
462
  }
463
464
  /**
465
   * Calls the listener when the given node is shown for the first time. The
466
   * visible property is not the same as the initial showing event; visibility
467
   * can be triggered numerous times (such as going off screen).
468
   * <p>
469
   * This is called, for example, before the drag handler can be attached,
470
   * because the scrollbar for the text editor pane must be visible.
471
   * </p>
472
   *
473
   * @param node     The node to watch for showing.
474
   * @param consumer The consumer to invoke when the event fires.
475
   */
476
  private void addShowListener(
477
      final Node node, final Consumer<Void> consumer ) {
478
    final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
479
        runLater( () -> {
480
          if( newShow ) {
481
            try {
482
              consumer.accept( null );
483
            } catch( final Exception ex ) {
484
              error( ex );
485
            }
486
          }
487
        } );
488
489
    Val.flatMap( node.sceneProperty(), Scene::windowProperty )
490
       .flatMap( Window::showingProperty )
491
       .addListener( listener );
492
  }
493
494
  private void scrollToParagraph( final int id ) {
495
    scrollToParagraph( id, false );
496
  }
497
498
  /**
499
   * @param id    The paragraph to scroll to, will be approximated if it doesn't
500
   *              exist.
501
   * @param force {@code true} means to force scrolling immediately, which
502
   *              should only be attempted when it is known that the document
503
   *              has been fully rendered. Otherwise the internal map of ID
504
   *              attributes will be incomplete and scrolling will flounder.
505
   */
506
  private void scrollToParagraph( final int id, final boolean force ) {
507
    synchronized( mMutex ) {
508
      final var previewPane = getPreviewPane();
509
      final var scrollPane = previewPane.getScrollPane();
510
      final int approxId = getActiveEditorPane().approximateParagraphId( id );
511
512
      if( force ) {
513
        previewPane.scrollTo( approxId );
514
      }
515
      else {
516
        previewPane.tryScrollTo( approxId );
517
      }
518
519
      scrollPane.repaint();
520
    }
521
  }
522
523
  private void updateVariableNameInjector( final FileEditorTab tab ) {
524
    getVariableNameInjector().addListener( tab );
525
  }
526
527
  /**
528
   * Called whenever the preview pane becomes out of sync with the file editor
529
   * tab. This can be called when the text changes, the caret paragraph
530
   * changes, or the file tab changes.
531
   *
532
   * @param tab The file editor tab that has been changed in some fashion.
533
   */
534
  private void process( final FileEditorTab tab ) {
535
    if( tab != null ) {
536
      getPreviewPane().setPath( tab.getPath() );
537
538
      final Processor<String> processor = getProcessors().computeIfAbsent(
539
          tab, p -> createProcessors( tab )
540
      );
541
542
      try {
543
        processChain( processor, tab.getEditorText() );
544
      } catch( final Exception ex ) {
545
        error( ex );
546
      }
547
    }
548
  }
549
550
  /**
551
   * Executes the processing chain, operating on the given string.
552
   *
553
   * @param handler The first processor in the chain to call.
554
   * @param text    The initial value of the text to process.
555
   * @return The final value of the text that was processed by the chain.
556
   */
557
  private String processChain( Processor<String> handler, String text ) {
558
    while( handler != null && text != null ) {
559
      text = handler.process( text );
560
      handler = handler.next();
561
    }
562
563
    return text;
564
  }
565
566
  private void renderActiveTab() {
567
    process( getActiveFileEditorTab() );
568
  }
569
570
  /**
571
   * Called when a definition source is opened.
572
   *
573
   * @param path Path to the definition source that was opened.
574
   */
575
  private void openDefinitions( final Path path ) {
576
    try {
577
      final var ds = createDefinitionSource( path );
578
      setDefinitionSource( ds );
579
580
      final var prefs = getUserPreferences();
581
      prefs.definitionPathProperty().setValue( path.toFile() );
582
      prefs.save();
583
584
      final var tooltipPath = new Tooltip( path.toString() );
585
      tooltipPath.setShowDelay( Duration.millis( 200 ) );
586
587
      final var pane = getDefinitionPane();
588
      pane.update( ds );
589
      pane.addTreeChangeHandler( mTreeHandler );
590
      pane.addKeyEventHandler( mDefinitionKeyHandler );
591
      pane.filenameProperty().setValue( path.getFileName().toString() );
592
      pane.setTooltip( tooltipPath );
593
594
      interpolateResolvedMap();
595
    } catch( final Exception ex ) {
596
      error( ex );
597
    }
598
  }
599
600
  private void exportDefinitions( final Path path ) {
601
    try {
602
      final DefinitionPane pane = getDefinitionPane();
603
      final TreeItem<String> root = pane.getTreeView().getRoot();
604
      final TreeItem<String> problemChild = pane.isTreeWellFormed();
605
606
      if( problemChild == null ) {
607
        getDefinitionSource().getTreeAdapter().export( root, path );
608
        getNotifier().clear();
609
      }
610
      else {
611
        final String msg = get(
612
            "yaml.error.tree.form", problemChild.getValue() );
613
        error( msg );
614
      }
615
    } catch( final Exception ex ) {
616
      error( ex );
617
    }
618
  }
619
620
  private void interpolateResolvedMap() {
621
    final Map<String, String> treeMap = getDefinitionPane().toMap();
622
    final Map<String, String> map = new HashMap<>( treeMap );
623
    MapInterpolator.interpolate( map );
624
625
    getResolvedMap().clear();
626
    getResolvedMap().putAll( map );
627
  }
628
629
  private void initDefinitionPane() {
630
    openDefinitions( getDefinitionPath() );
631
  }
632
633
  /**
634
   * Called when an exception occurs that warrants the user's attention.
635
   *
636
   * @param ex The exception with a message that the user should know about.
637
   */
638
  private void error( final Exception ex ) {
639
    getNotifier().notify( ex );
640
  }
641
642
  private void error( final String msg ) {
643
    getNotifier().notify( msg );
644
  }
645
646
  //---- File actions -------------------------------------------------------
647
648
  /**
649
   * Called when an {@link Observable} instance has changed. This is called
650
   * by both the {@link Snitch} service and the notify service. The @link
651
   * Snitch} service can be called for different file types, including
652
   * {@link DefinitionSource} instances.
653
   *
654
   * @param observable The observed instance.
655
   * @param value      The noteworthy item.
656
   */
657
  @Override
658
  public void update( final Observable observable, final Object value ) {
659
    if( value != null ) {
660
      if( observable instanceof Snitch && value instanceof Path ) {
661
        updateSelectedTab();
662
      }
663
      else if( observable instanceof Notifier && value instanceof String ) {
664
        updateStatusBar( (String) value );
665
      }
666
    }
667
  }
668
669
  /**
670
   * Updates the status bar to show the given message.
671
   *
672
   * @param s The message to show in the status bar.
673
   */
674
  private void updateStatusBar( final String s ) {
675
    runLater(
676
        () -> {
677
          final int index = s.indexOf( '\n' );
678
          final String message = s.substring(
679
              0, index > 0 ? index : s.length() );
680
681
          getStatusBar().setText( message );
682
        }
683
    );
684
  }
685
686
  /**
687
   * Called when a file has been modified.
688
   */
689
  private void updateSelectedTab() {
690
    runLater(
691
        () -> {
692
          // Brute-force XSLT file reload by re-instantiating all processors.
693
          resetProcessors();
694
          renderActiveTab();
695
        }
696
    );
697
  }
698
699
  /**
700
   * After resetting the processors, they will refresh anew to be up-to-date
701
   * with the files (text and definition) currently loaded into the editor.
702
   */
703
  private void resetProcessors() {
704
    getProcessors().clear();
705
  }
706
707
  //---- File actions -------------------------------------------------------
708
709
  private void fileNew() {
710
    getFileEditorPane().newEditor();
711
  }
712
713
  private void fileOpen() {
714
    getFileEditorPane().openFileDialog();
715
  }
716
717
  private void fileClose() {
718
    getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
719
  }
720
721
  /**
722
   * TODO: Upon closing, first remove the tab change listeners. (There's no
723
   * need to re-render each tab when all are being closed.)
724
   */
725
  private void fileCloseAll() {
726
    getFileEditorPane().closeAllEditors();
727
  }
728
729
  private void fileSave() {
730
    getFileEditorPane().saveEditor( getActiveFileEditorTab() );
731
  }
732
733
  private void fileSaveAs() {
734
    final FileEditorTab editor = getActiveFileEditorTab();
735
    getFileEditorPane().saveEditorAs( editor );
736
    getProcessors().remove( editor );
737
738
    try {
739
      process( editor );
740
    } catch( final Exception ex ) {
741
      error( ex );
742
    }
743
  }
744
745
  private void fileSaveAll() {
746
    getFileEditorPane().saveAllEditors();
747
  }
748
749
  private void fileExit() {
750
    final Window window = getWindow();
751
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
752
  }
753
754
  //---- Edit actions -------------------------------------------------------
755
756
  /**
757
   * Transform the Markdown into HTML then copy that HTML into the copy
758
   * buffer.
759
   */
760
  private void copyHtml() {
761
    final var markdown = getActiveEditorPane().getText();
762
    final var processors = createProcessorFactory().createProcessors(
763
        getActiveFileEditorTab()
764
    );
765
766
    final var chain = processors.remove( HtmlPreviewProcessor.class );
767
768
    final String html = processChain( chain, markdown );
769
770
    final Clipboard clipboard = Clipboard.getSystemClipboard();
771
    final ClipboardContent content = new ClipboardContent();
772
    content.putString( html );
773
    clipboard.setContent( content );
774
  }
775
776
  /**
777
   * Used to find text in the active file editor window.
778
   */
779
  private void editFind() {
780
    final TextField input = getFindTextField();
781
    getStatusBar().setGraphic( input );
782
    input.requestFocus();
783
  }
784
785
  public void editFindNext() {
786
    getActiveFileEditorTab().searchNext( getFindTextField().getText() );
787
  }
788
789
  public void editPreferences() {
790
    getUserPreferences().show();
791
  }
792
793
  //---- Insert actions -----------------------------------------------------
794
795
  /**
796
   * Delegates to the active editor to handle wrapping the current text
797
   * selection with leading and trailing strings.
798
   *
799
   * @param leading  The string to put before the selection.
800
   * @param trailing The string to put after the selection.
801
   */
802
  private void insertMarkdown(
803
      final String leading, final String trailing ) {
804
    getActiveEditorPane().surroundSelection( leading, trailing );
805
  }
806
807
  private void insertMarkdown(
808
      final String leading, final String trailing, final String hint ) {
809
    getActiveEditorPane().surroundSelection( leading, trailing, hint );
810
  }
811
812
  //---- Help actions -------------------------------------------------------
813
814
  private void helpAbout() {
815
    final Alert alert = new Alert( AlertType.INFORMATION );
816
    alert.setTitle( get( "Dialog.about.title" ) );
817
    alert.setHeaderText( get( "Dialog.about.header" ) );
818
    alert.setContentText( get( "Dialog.about.content" ) );
819
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
820
    alert.initOwner( getWindow() );
821
822
    alert.showAndWait();
823
  }
824
825
  //---- Member creators ----------------------------------------------------
826
827
  private SpellChecker createSpellChecker() {
828
    try {
829
      final Collection<String> lexicon = readLexicon( "en.txt" );
830
      return SymSpellSpeller.forLexicon( lexicon );
831
    } catch( final Exception ex ) {
832
      error( ex );
833
      return new PermissiveSpeller();
834
    }
835
  }
836
837
  /**
838
   * Factory to create processors that are suited to different file types.
839
   *
840
   * @param tab The tab that is subjected to processing.
841
   * @return A processor suited to the file type specified by the tab's path.
842
   */
843
  private Processor<String> createProcessors( final FileEditorTab tab ) {
844
    return createProcessorFactory().createProcessors( tab );
845
  }
846
847
  private ProcessorFactory createProcessorFactory() {
848
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
849
  }
850
851
  private HTMLPreviewPane createHTMLPreviewPane() {
852
    return new HTMLPreviewPane();
853
  }
854
855
  private DefinitionSource createDefaultDefinitionSource() {
856
    return new YamlDefinitionSource( getDefinitionPath() );
857
  }
858
859
  private DefinitionSource createDefinitionSource( final Path path ) {
860
    try {
861
      return createDefinitionFactory().createDefinitionSource( path );
862
    } catch( final Exception ex ) {
863
      error( ex );
864
      return createDefaultDefinitionSource();
865
    }
866
  }
867
868
  private TextField createFindTextField() {
869
    return new TextField();
870
  }
871
872
  private DefinitionFactory createDefinitionFactory() {
873
    return new DefinitionFactory();
874
  }
875
876
  private StatusBar createStatusBar() {
877
    return new StatusBar();
878
  }
879
880
  private Scene createScene() {
881
    final SplitPane splitPane = new SplitPane(
882
        getDefinitionPane().getNode(),
883
        getFileEditorPane().getNode(),
884
        getPreviewPane().getNode() );
885
886
    splitPane.setDividerPositions(
887
        getFloat( K_PANE_SPLIT_DEFINITION, .22f ),
888
        getFloat( K_PANE_SPLIT_EDITOR, .60f ),
889
        getFloat( K_PANE_SPLIT_PREVIEW, .18f ) );
890
891
    getDefinitionPane().prefHeightProperty()
892
                       .bind( splitPane.heightProperty() );
893
894
    final BorderPane borderPane = new BorderPane();
895
    borderPane.setPrefSize( 1280, 800 );
896
    borderPane.setTop( createMenuBar() );
897
    borderPane.setBottom( getStatusBar() );
898
    borderPane.setCenter( splitPane );
899
900
    final VBox statusBar = new VBox();
901
    statusBar.setAlignment( Pos.BASELINE_CENTER );
902
    statusBar.getChildren().add( getLineNumberText() );
903
    getStatusBar().getRightItems().add( statusBar );
904
905
    // Force preview pane refresh on Windows.
906
    if( SystemUtils.IS_OS_WINDOWS ) {
907
      splitPane.getDividers().get( 1 ).positionProperty().addListener(
908
          ( l, oValue, nValue ) -> runLater(
909
              () -> getPreviewPane().getScrollPane().repaint()
910
          )
911
      );
912
    }
913
914
    return new Scene( borderPane );
915
  }
916
917
  private Text createLineNumberText() {
918
    return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
919
  }
920
921
  private Node createMenuBar() {
922
    final BooleanBinding activeFileEditorIsNull =
923
        getFileEditorPane().activeFileEditorProperty().isNull();
924
925
    // File actions
926
    final Action fileNewAction = new ActionBuilder()
927
        .setText( "Main.menu.file.new" )
928
        .setAccelerator( "Shortcut+N" )
929
        .setIcon( FILE_ALT )
930
        .setAction( e -> fileNew() )
931
        .build();
932
    final Action fileOpenAction = new ActionBuilder()
933
        .setText( "Main.menu.file.open" )
934
        .setAccelerator( "Shortcut+O" )
935
        .setIcon( FOLDER_OPEN_ALT )
936
        .setAction( e -> fileOpen() )
937
        .build();
938
    final Action fileCloseAction = new ActionBuilder()
939
        .setText( "Main.menu.file.close" )
940
        .setAccelerator( "Shortcut+W" )
941
        .setAction( e -> fileClose() )
942
        .setDisable( activeFileEditorIsNull )
943
        .build();
944
    final Action fileCloseAllAction = new ActionBuilder()
945
        .setText( "Main.menu.file.close_all" )
946
        .setAction( e -> fileCloseAll() )
947
        .setDisable( activeFileEditorIsNull )
948
        .build();
949
    final Action fileSaveAction = new ActionBuilder()
950
        .setText( "Main.menu.file.save" )
951
        .setAccelerator( "Shortcut+S" )
952
        .setIcon( FLOPPY_ALT )
953
        .setAction( e -> fileSave() )
954
        .setDisable( createActiveBooleanProperty(
955
            FileEditorTab::modifiedProperty ).not() )
956
        .build();
957
    final Action fileSaveAsAction = new ActionBuilder()
958
        .setText( "Main.menu.file.save_as" )
959
        .setAction( e -> fileSaveAs() )
960
        .setDisable( activeFileEditorIsNull )
961
        .build();
962
    final Action fileSaveAllAction = new ActionBuilder()
963
        .setText( "Main.menu.file.save_all" )
964
        .setAccelerator( "Shortcut+Shift+S" )
965
        .setAction( e -> fileSaveAll() )
966
        .setDisable( Bindings.not(
967
            getFileEditorPane().anyFileEditorModifiedProperty() ) )
968
        .build();
969
    final Action fileExitAction = new ActionBuilder()
970
        .setText( "Main.menu.file.exit" )
971
        .setAction( e -> fileExit() )
972
        .build();
973
974
    // Edit actions
975
    final Action editCopyHtmlAction = new ActionBuilder()
976
        .setText( Messages.get( "Main.menu.edit.copy.html" ) )
977
        .setIcon( HTML5 )
978
        .setAction( e -> copyHtml() )
979
        .setDisable( activeFileEditorIsNull )
980
        .build();
981
982
    final Action editUndoAction = new ActionBuilder()
983
        .setText( "Main.menu.edit.undo" )
984
        .setAccelerator( "Shortcut+Z" )
985
        .setIcon( UNDO )
986
        .setAction( e -> getActiveEditorPane().undo() )
987
        .setDisable( createActiveBooleanProperty(
988
            FileEditorTab::canUndoProperty ).not() )
989
        .build();
990
    final Action editRedoAction = new ActionBuilder()
991
        .setText( "Main.menu.edit.redo" )
992
        .setAccelerator( "Shortcut+Y" )
993
        .setIcon( REPEAT )
994
        .setAction( e -> getActiveEditorPane().redo() )
995
        .setDisable( createActiveBooleanProperty(
996
            FileEditorTab::canRedoProperty ).not() )
997
        .build();
998
999
    final Action editCutAction = new ActionBuilder()
1000
        .setText( Messages.get( "Main.menu.edit.cut" ) )
1001
        .setAccelerator( "Shortcut+X" )
1002
        .setIcon( CUT )
1003
        .setAction( e -> getActiveEditorPane().cut() )
1004
        .setDisable( activeFileEditorIsNull )
1005
        .build();
1006
    final Action editCopyAction = new ActionBuilder()
1007
        .setText( Messages.get( "Main.menu.edit.copy" ) )
1008
        .setAccelerator( "Shortcut+C" )
1009
        .setIcon( COPY )
1010
        .setAction( e -> getActiveEditorPane().copy() )
1011
        .setDisable( activeFileEditorIsNull )
1012
        .build();
1013
    final Action editPasteAction = new ActionBuilder()
1014
        .setText( Messages.get( "Main.menu.edit.paste" ) )
1015
        .setAccelerator( "Shortcut+V" )
1016
        .setIcon( PASTE )
1017
        .setAction( e -> getActiveEditorPane().paste() )
1018
        .setDisable( activeFileEditorIsNull )
1019
        .build();
1020
    final Action editSelectAllAction = new ActionBuilder()
1021
        .setText( Messages.get( "Main.menu.edit.selectAll" ) )
1022
        .setAccelerator( "Shortcut+A" )
1023
        .setAction( e -> getActiveEditorPane().selectAll() )
1024
        .setDisable( activeFileEditorIsNull )
1025
        .build();
1026
1027
    final Action editFindAction = new ActionBuilder()
1028
        .setText( "Main.menu.edit.find" )
1029
        .setAccelerator( "Ctrl+F" )
1030
        .setIcon( SEARCH )
1031
        .setAction( e -> editFind() )
1032
        .setDisable( activeFileEditorIsNull )
1033
        .build();
1034
    final Action editFindNextAction = new ActionBuilder()
1035
        .setText( "Main.menu.edit.find.next" )
1036
        .setAccelerator( "F3" )
1037
        .setIcon( null )
1038
        .setAction( e -> editFindNext() )
1039
        .setDisable( activeFileEditorIsNull )
1040
        .build();
1041
    final Action editPreferencesAction = new ActionBuilder()
1042
        .setText( "Main.menu.edit.preferences" )
1043
        .setAccelerator( "Ctrl+Alt+S" )
1044
        .setAction( e -> editPreferences() )
1045
        .build();
1046
1047
    // Insert actions
1048
    final Action insertBoldAction = new ActionBuilder()
1049
        .setText( "Main.menu.insert.bold" )
1050
        .setAccelerator( "Shortcut+B" )
1051
        .setIcon( BOLD )
1052
        .setAction( e -> insertMarkdown( "**", "**" ) )
1053
        .setDisable( activeFileEditorIsNull )
1054
        .build();
1055
    final Action insertItalicAction = new ActionBuilder()
1056
        .setText( "Main.menu.insert.italic" )
1057
        .setAccelerator( "Shortcut+I" )
1058
        .setIcon( ITALIC )
1059
        .setAction( e -> insertMarkdown( "*", "*" ) )
1060
        .setDisable( activeFileEditorIsNull )
1061
        .build();
1062
    final Action insertSuperscriptAction = new ActionBuilder()
1063
        .setText( "Main.menu.insert.superscript" )
1064
        .setAccelerator( "Shortcut+[" )
1065
        .setIcon( SUPERSCRIPT )
1066
        .setAction( e -> insertMarkdown( "^", "^" ) )
1067
        .setDisable( activeFileEditorIsNull )
1068
        .build();
1069
    final Action insertSubscriptAction = new ActionBuilder()
1070
        .setText( "Main.menu.insert.subscript" )
1071
        .setAccelerator( "Shortcut+]" )
1072
        .setIcon( SUBSCRIPT )
1073
        .setAction( e -> insertMarkdown( "~", "~" ) )
1074
        .setDisable( activeFileEditorIsNull )
1075
        .build();
1076
    final Action insertStrikethroughAction = new ActionBuilder()
1077
        .setText( "Main.menu.insert.strikethrough" )
1078
        .setAccelerator( "Shortcut+T" )
1079
        .setIcon( STRIKETHROUGH )
1080
        .setAction( e -> insertMarkdown( "~~", "~~" ) )
1081
        .setDisable( activeFileEditorIsNull )
1082
        .build();
1083
    final Action insertBlockquoteAction = new ActionBuilder()
1084
        .setText( "Main.menu.insert.blockquote" )
1085
        .setAccelerator( "Ctrl+Q" )
1086
        .setIcon( QUOTE_LEFT )
1087
        .setAction( e -> insertMarkdown( "\n\n> ", "" ) )
1088
        .setDisable( activeFileEditorIsNull )
1089
        .build();
1090
    final Action insertCodeAction = new ActionBuilder()
1091
        .setText( "Main.menu.insert.code" )
1092
        .setAccelerator( "Shortcut+K" )
1093
        .setIcon( CODE )
1094
        .setAction( e -> insertMarkdown( "`", "`" ) )
1095
        .setDisable( activeFileEditorIsNull )
1096
        .build();
1097
    final Action insertFencedCodeBlockAction = new ActionBuilder()
1098
        .setText( "Main.menu.insert.fenced_code_block" )
1099
        .setAccelerator( "Shortcut+Shift+K" )
1100
        .setIcon( FILE_CODE_ALT )
1101
        .setAction( e -> insertMarkdown(
1102
            "\n\n```\n",
1103
            "\n```\n\n",
1104
            get( "Main.menu.insert.fenced_code_block.prompt" ) ) )
1105
        .setDisable( activeFileEditorIsNull )
1106
        .build();
1107
    final Action insertLinkAction = new ActionBuilder()
1108
        .setText( "Main.menu.insert.link" )
1109
        .setAccelerator( "Shortcut+L" )
1110
        .setIcon( LINK )
1111
        .setAction( e -> getActiveEditorPane().insertLink() )
1112
        .setDisable( activeFileEditorIsNull )
1113
        .build();
1114
    final Action insertImageAction = new ActionBuilder()
1115
        .setText( "Main.menu.insert.image" )
1116
        .setAccelerator( "Shortcut+G" )
1117
        .setIcon( PICTURE_ALT )
1118
        .setAction( e -> getActiveEditorPane().insertImage() )
1119
        .setDisable( activeFileEditorIsNull )
1120
        .build();
1121
1122
    // Number of header actions (H1 ... H3)
1123
    final int HEADERS = 3;
1124
    final Action[] headers = new Action[ HEADERS ];
1125
1126
    for( int i = 1; i <= HEADERS; i++ ) {
1127
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
1128
      final String markup = String.format( "%n%n%s ", hashes );
1129
      final String text = "Main.menu.insert.header." + i;
1130
      final String accelerator = "Shortcut+" + i;
1131
      final String prompt = text + ".prompt";
1132
1133
      headers[ i - 1 ] = new ActionBuilder()
1134
          .setText( text )
1135
          .setAccelerator( accelerator )
1136
          .setIcon( HEADER )
1137
          .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) )
1138
          .setDisable( activeFileEditorIsNull )
1139
          .build();
1140
    }
1141
1142
    final Action insertUnorderedListAction = new ActionBuilder()
1143
        .setText( "Main.menu.insert.unordered_list" )
1144
        .setAccelerator( "Shortcut+U" )
1145
        .setIcon( LIST_UL )
1146
        .setAction( e -> insertMarkdown( "\n\n* ", "" ) )
1147
        .setDisable( activeFileEditorIsNull )
1148
        .build();
1149
    final Action insertOrderedListAction = new ActionBuilder()
1150
        .setText( "Main.menu.insert.ordered_list" )
1151
        .setAccelerator( "Shortcut+Shift+O" )
1152
        .setIcon( LIST_OL )
1153
        .setAction( e -> insertMarkdown(
1154
            "\n\n1. ", "" ) )
1155
        .setDisable( activeFileEditorIsNull )
1156
        .build();
1157
    final Action insertHorizontalRuleAction = new ActionBuilder()
1158
        .setText( "Main.menu.insert.horizontal_rule" )
1159
        .setAccelerator( "Shortcut+H" )
1160
        .setAction( e -> insertMarkdown(
1161
            "\n\n---\n\n", "" ) )
1162
        .setDisable( activeFileEditorIsNull )
1163
        .build();
1164
1165
    // Help actions
1166
    final Action helpAboutAction = new ActionBuilder()
1167
        .setText( "Main.menu.help.about" )
1168
        .setAction( e -> helpAbout() )
1169
        .build();
1170
1171
    //---- MenuBar ----
1172
    final Menu fileMenu = ActionUtils.createMenu(
1173
        get( "Main.menu.file" ),
1174
        fileNewAction,
1175
        fileOpenAction,
1176
        null,
1177
        fileCloseAction,
1178
        fileCloseAllAction,
1179
        null,
1180
        fileSaveAction,
1181
        fileSaveAsAction,
1182
        fileSaveAllAction,
1183
        null,
1184
        fileExitAction );
1185
1186
    final Menu editMenu = ActionUtils.createMenu(
1187
        get( "Main.menu.edit" ),
1188
        editCopyHtmlAction,
1189
        null,
1190
        editUndoAction,
1191
        editRedoAction,
1192
        null,
1193
        editCutAction,
1194
        editCopyAction,
1195
        editPasteAction,
1196
        editSelectAllAction,
1197
        null,
1198
        editFindAction,
1199
        editFindNextAction,
1200
        null,
1201
        editPreferencesAction );
1202
1203
    final Menu insertMenu = ActionUtils.createMenu(
1204
        get( "Main.menu.insert" ),
1205
        insertBoldAction,
1206
        insertItalicAction,
1207
        insertSuperscriptAction,
1208
        insertSubscriptAction,
1209
        insertStrikethroughAction,
1210
        insertBlockquoteAction,
1211
        insertCodeAction,
1212
        insertFencedCodeBlockAction,
1213
        null,
1214
        insertLinkAction,
1215
        insertImageAction,
1216
        null,
1217
        headers[ 0 ],
1218
        headers[ 1 ],
1219
        headers[ 2 ],
1220
        null,
1221
        insertUnorderedListAction,
1222
        insertOrderedListAction,
1223
        insertHorizontalRuleAction
1224
    );
1225
1226
    final Menu helpMenu = ActionUtils.createMenu(
1227
        get( "Main.menu.help" ),
1228
        helpAboutAction );
1229
1230
    final MenuBar menuBar = new MenuBar(
1231
        fileMenu,
1232
        editMenu,
1233
        insertMenu,
1234
        helpMenu );
1235
1236
    //---- ToolBar ----
1237
    final ToolBar toolBar = ActionUtils.createToolBar(
1238
        fileNewAction,
1239
        fileOpenAction,
1240
        fileSaveAction,
1241
        null,
1242
        editUndoAction,
1243
        editRedoAction,
1244
        editCutAction,
1245
        editCopyAction,
1246
        editPasteAction,
1247
        null,
1248
        insertBoldAction,
1249
        insertItalicAction,
1250
        insertSuperscriptAction,
1251
        insertSubscriptAction,
1252
        insertBlockquoteAction,
1253
        insertCodeAction,
1254
        insertFencedCodeBlockAction,
1255
        null,
1256
        insertLinkAction,
1257
        insertImageAction,
1258
        null,
1259
        headers[ 0 ],
1260
        null,
1261
        insertUnorderedListAction,
1262
        insertOrderedListAction );
1263
1264
    return new VBox( menuBar, toolBar );
1265
  }
1266
1267
  /**
1268
   * Creates a boolean property that is bound to another boolean value of the
1269
   * active editor.
1270
   */
1271
  private BooleanProperty createActiveBooleanProperty(
1272
      final Function<FileEditorTab, ObservableBooleanValue> func ) {
1273
1274
    final BooleanProperty b = new SimpleBooleanProperty();
1275
    final FileEditorTab tab = getActiveFileEditorTab();
1276
1277
    if( tab != null ) {
1278
      b.bind( func.apply( tab ) );
1279
    }
1280
1281
    getFileEditorPane().activeFileEditorProperty().addListener(
1282
        ( observable, oldFileEditor, newFileEditor ) -> {
1283
          b.unbind();
1284
1285
          if( newFileEditor == null ) {
1286
            b.set( false );
1287
          }
1288
          else {
1289
            b.bind( func.apply( newFileEditor ) );
1290
          }
1291
        }
1292
    );
1293
1294
    return b;
1295
  }
1296
1297
  //---- Convenience accessors ----------------------------------------------
1298
1299
  private Preferences getPreferences() {
1300
    return sOptions.getState();
1301
  }
1302
1303
  private int getCurrentParagraphIndex() {
1304
    return getActiveEditorPane().getCurrentParagraphIndex();
1305
  }
1306
1307
  private float getFloat( final String key, final float defaultValue ) {
1308
    return getPreferences().getFloat( key, defaultValue );
1309
  }
1310
1311
  public Window getWindow() {
1312
    return getScene().getWindow();
1313
  }
1314
1315
  private MarkdownEditorPane getActiveEditorPane() {
1316
    return getActiveFileEditorTab().getEditorPane();
1317
  }
1318
1319
  private FileEditorTab getActiveFileEditorTab() {
1320
    return getFileEditorPane().getActiveFileEditor();
1321
  }
1322
1323
  //---- Member accessors ---------------------------------------------------
1324
1325
  protected Scene getScene() {
1326
    return mScene;
1327
  }
1328
1329
  private SpellChecker getSpellChecker() {
1330
    return mSpellChecker;
1331
  }
1332
1333
  private Map<FileEditorTab, Processor<String>> getProcessors() {
1334
    return mProcessors;
1335
  }
1336
1337
  private FileEditorTabPane getFileEditorPane() {
1338
    return mFileEditorPane;
1339
  }
1340
1341
  private HTMLPreviewPane getPreviewPane() {
1342
    return mPreviewPane;
1343
  }
1344
1345
  private void setDefinitionSource(
1346
      final DefinitionSource definitionSource ) {
1347
    assert definitionSource != null;
1348
    mDefinitionSource = definitionSource;
1349
  }
1350
1351
  private DefinitionSource getDefinitionSource() {
1352
    return mDefinitionSource;
1353
  }
1354
1355
  private DefinitionPane getDefinitionPane() {
1356
    return mDefinitionPane;
1357
  }
1358
1359
  private Text getLineNumberText() {
1360
    return mLineNumberText;
1361
  }
1362
1363
  private StatusBar getStatusBar() {
1364
    return mStatusBar;
1365
  }
1366
1367
  private TextField getFindTextField() {
1368
    return mFindTextField;
1369
  }
1370
1371
  private VariableNameInjector getVariableNameInjector() {
1372
    return mVariableNameInjector;
1373
  }
1374
1375
  /**
1376
   * Returns the variable map of interpolated definitions.
1377
   *
1378
   * @return A map to help dereference variables.
1379
   */
1380
  private Map<String, String> getResolvedMap() {
1381
    return mResolvedMap;
1382
  }
1383
1384
  private Notifier getNotifier() {
1385
    return sNotifier;
1386
  }
1387
1388
  //---- Persistence accessors ----------------------------------------------
1389
1390
  private UserPreferences getUserPreferences() {
1391
    return sOptions.getUserPreferences();
1392
  }
1393
1394
  private Path getDefinitionPath() {
1395
    return getUserPreferences().getDefinitionPath();
1396
  }
1397
1398
  //---- Spelling -----------------------------------------------------------
1399
1400
  /**
1401
   * Delegates to {@link #spellcheck(StyleClassedTextArea, String, int)}.
1402
   *
1403
   * @param text The full document text.
1404
   */
1405
  private void spellcheck(
1406
      final StyleClassedTextArea editor, final String text ) {
1407
    spellcheck( editor, text, -1 );
1408
  }
1409
1410
  /**
1411
   * @param text   Look up words for this text in the lexicon.
1412
   * @param paraId Set to -1 to apply resulting style spans to the entire
1413
   *               text.
1414
   */
1415
  private void spellcheck(
1416
      final StyleClassedTextArea editor, final String text, final int paraId ) {
1417
    final var builder = new StyleSpansBuilder<Collection<String>>();
1418
    final var runningIndex = new AtomicInteger( 0 );
1419
1420
    getSpellChecker().proofread( text, ( prevIndex, currIndex ) -> {
1421
      // Clear styling between lexiconically absent words.
1422
      builder.add( emptyList(), prevIndex - runningIndex.get() );
1423
      builder.add( singleton( "spelling" ), currIndex - prevIndex );
1424
      runningIndex.set( currIndex );
1425
    } );
1426
1427
    // If the running index was set, at least one word triggered the listener.
1428
    if( runningIndex.get() > 0 ) {
1429
      // Clear styling after the last lexiconically absent word.
1430
      builder.add( emptyList(), text.length() - runningIndex.get() );
1431
1432
      final var spans = builder.create();
1433
1434
      if( paraId >= 0 ) {
1435
        editor.setStyleSpans( paraId, 0, spans );
1436
      }
1437
      else {
1438
        editor.setStyleSpans( 0, spans );
1439
      }
1440
    }
1441
  }
13661442
13671443
  @SuppressWarnings("SameParameterValue")
M src/main/java/com/scrivenvar/decorators/RVariableDecorator.java
2828
package com.scrivenvar.decorators;
2929
30
import com.scrivenvar.Services;
31
import com.scrivenvar.preferences.UserPreferences;
32
import com.scrivenvar.service.Options;
33
3034
/**
3135
 * Brackets variable names with {@code `r#} and {@code `}.
3236
 */
3337
public class RVariableDecorator implements VariableDecorator {
38
  private static final Options sOptions = Services.load( Options.class );
39
3440
  public static final String PREFIX = "`r#";
3541
  public static final char SUFFIX = '`';
42
43
  private final String mDelimiterBegan;
44
  private final String mDelimiterEnded;
45
46
  public RVariableDecorator() {
47
    final var prefs = getUserPreferences();
48
    mDelimiterBegan = prefs.getRDelimiterBegan();
49
    mDelimiterEnded = prefs.getRDelimiterEnded();
50
  }
3651
3752
  /**
3853
   * Returns the given string R-escaping backticks prepended and appended. This
3954
   * is not null safe. Do not pass null into this method.
4055
   *
4156
   * @param variableName The string to decorate.
42
   * @return "`r#" + variableName + "`".
57
   * @return "`r#" + delimiterBegan + variableName+ delimiterEnded + "`".
4358
   */
4459
  @Override
...
5166
    }
5267
53
    return PREFIX +
54
        "x( v$" +
55
        variableName.replace( '.', '$' ) +
56
        " )" +
57
        SUFFIX;
68
    return PREFIX
69
        + mDelimiterBegan
70
        + "v$"
71
        + variableName.replace( '.', '$' )
72
        + mDelimiterEnded
73
        + SUFFIX;
74
  }
75
76
  private UserPreferences getUserPreferences() {
77
    return sOptions.getUserPreferences();
5878
  }
5979
}
M src/main/java/com/scrivenvar/preferences/UserPreferences.java
4545
import java.nio.file.Path;
4646
47
import static com.scrivenvar.Constants.PERSIST_IMAGES_DEFAULT;
48
import static com.scrivenvar.Constants.USER_DIRECTORY;
47
import static com.scrivenvar.Constants.*;
4948
import static com.scrivenvar.Messages.get;
5049
...
6160
  private final StringProperty mPropImagesOrder;
6261
  private final ObjectProperty<File> mPropDefinitionPath;
62
  private final StringProperty mRDelimiterBegan;
63
  private final StringProperty mRDelimiterEnded;
6364
6465
  private final PreferencesFx mPreferencesFx;
...
7475
        "file.definition.default", "variables.yaml" )
7576
    );
77
78
    mRDelimiterBegan = new SimpleStringProperty( R_DELIMITER_BEGAN_DEFAULT );
79
    mRDelimiterEnded = new SimpleStringProperty( R_DELIMITER_ENDED_DEFAULT );
7680
7781
    mPreferencesFx = createPreferencesFx();
...
122126
                Setting.of( label( "Preferences.r.script.desc" ) ),
123127
                scriptSetting
128
            ),
129
            Group.of(
130
                get( "Preferences.r.delimiter.began" ),
131
                Setting.of( label( "Preferences.r.delimiter.began.desc" ) ),
132
                Setting.of( "Opening", mRDelimiterBegan )
133
            ),
134
            Group.of(
135
                get( "Preferences.r.delimiter.ended" ),
136
                Setting.of( label( "Preferences.r.delimiter.ended.desc" ) ),
137
                Setting.of( "Closing", mRDelimiterEnded )
124138
            )
125139
        ),
...
217231
  public String getRScript() {
218232
    return rScriptProperty().getValue();
233
  }
234
235
  private StringProperty rDelimiterBegan() {
236
    return mRDelimiterBegan;
237
  }
238
239
  public String getRDelimiterBegan() {
240
    return rDelimiterBegan().get();
241
  }
242
243
  private StringProperty rDelimiterEnded() {
244
    return mRDelimiterEnded;
245
  }
246
247
  public String getRDelimiterEnded() {
248
    return rDelimiterEnded().get();
219249
  }
220250
M src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
110110
    @Override
111111
    public void componentResized( final ComponentEvent e ) {
112
      // Scaling a bit below the full width prevents the horizontal scrollbar
113
      // from appearing.
114
      final int width = (int) (e.getComponent().getWidth() * .95);
115
      HTMLPreviewPane.this.mImageLoader.widthProperty().set( width );
112
      setWidth( e );
116113
    }
117114
118115
    @Override
119
    public void componentMoved( final ComponentEvent e ) {
116
    public void componentShown( final ComponentEvent e ) {
117
      setWidth( e );
120118
    }
121119
122120
    @Override
123
    public void componentShown( final ComponentEvent e ) {
121
    public void componentMoved( final ComponentEvent e ) {
124122
    }
125123
126124
    @Override
127125
    public void componentHidden( final ComponentEvent e ) {
126
    }
127
128
    /**
129
     * Sets the width of the {@link HTMLPreviewPane} so that images can be
130
     * scaled to fit. The scale factor is adjusted a bit below the full width
131
     * to prevent the horizontal scrollbar from appearing.
132
     *
133
     * @param e The component that defines the image scaling width.
134
     */
135
    private void setWidth( final ComponentEvent e ) {
136
      final int width = (int) (e.getComponent().getWidth() * .9);
137
      HTMLPreviewPane.this.mImageLoader.widthProperty().set( width );
128138
    }
129139
  }
M src/main/java/com/scrivenvar/processors/InlineRProcessor.java
5252
public final class InlineRProcessor extends DefinitionProcessor {
5353
54
  private static final Notifier NOTIFIER = Services.load( Notifier.class );
55
  private static final Options OPTIONS = Services.load( Options.class );
54
  private static final Notifier sNotifier = Services.load( Notifier.class );
55
  private static final Options sOptions = Services.load( Options.class );
5656
5757
  /**
...
107107
        eval( replace( bootstrap, map ) );
108108
      }
109
    } catch( final Exception e ) {
110
      getNotifier().notify( e );
109
    } catch( final Exception ex ) {
110
      getNotifier().notify( ex );
111111
    }
112112
  }
...
196196
    try {
197197
      return getScriptEngine().eval( r );
198
    } catch( final ScriptException e ) {
199
      getNotifier().notify( e );
198
    } catch( final ScriptException ex ) {
199
      getNotifier().notify( ex );
200200
      return "";
201201
    }
...
222222
223223
  private UserPreferences getUserPreferences() {
224
    return getOptions().getUserPreferences();
224
    return sOptions.getUserPreferences();
225225
  }
226226
227227
  private ScriptEngine getScriptEngine() {
228228
    return ENGINE;
229229
  }
230230
231231
  private Notifier getNotifier() {
232
    return NOTIFIER;
233
  }
234
235
  private Options getOptions() {
236
    return OPTIONS;
232
    return sNotifier;
237233
  }
238234
}
M src/main/java/com/scrivenvar/processors/markdown/ImageLinkExtension.java
107107
108108
      try {
109
        // If the direct file name exists, then use it directly.
110
        if( Path.of( url ).toFile().exists() ) {
111
          return valid( link, url );
112
        }
113
      } catch( final Exception ignored ) {
114
        // Try to dynamically resolve the image.
115
      }
116
117
      try {
109118
        final Path imagePrefix = getImagePrefix().toPath();
110119
...
149158
        getNotifier().clear();
150159
151
        return link.withStatus( LinkStatus.VALID ).withUrl( url );
160
        return valid( link, url );
152161
      } catch( final Exception e ) {
153162
        getNotifier().notify( "File not found: " + e.getLocalizedMessage() );
154163
      }
155164
156165
      return link;
166
    }
167
168
    private ResolvedLink valid( final ResolvedLink link, final String url ) {
169
      return link.withStatus( LinkStatus.VALID ).withUrl( url );
157170
    }
158171
A src/main/java/com/scrivenvar/spelling/api/SpellCheckListener.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.spelling.api;
29
30
import java.util.function.BiConsumer;
31
32
/**
33
 * Represents an operation that accepts two input arguments and returns no
34
 * result. Unlike most other functional interfaces, {@code BiConsumer} is
35
 * expected to operate via side-effects.
36
 * <p>
37
 * This is used instead of a {@link BiConsumer} to avoid autoboxing.
38
 * </p>
39
 */
40
@FunctionalInterface
41
public interface SpellCheckListener {
42
43
  /**
44
   * Performs an operation on the given arguments.
45
   *
46
   * @param i1 the first input argument
47
   * @param i2 the second input argument
48
   */
49
  void accept( int i1, int i2 );
50
}
151
M src/main/java/com/scrivenvar/spelling/api/SpellChecker.java
2929
3030
import java.util.List;
31
import java.util.function.BiConsumer;
3231
3332
/**
...
6665
   *                 and ending offset into the text where said word is found.
6766
   */
68
  void proofread( String text, BiConsumer<Integer, Integer> consumer );
67
  void proofread( String text, SpellCheckListener consumer );
6968
}
7069
M src/main/java/com/scrivenvar/spelling/impl/PermissiveSpeller.java
2828
package com.scrivenvar.spelling.impl;
2929
30
import com.scrivenvar.spelling.api.SpellCheckListener;
3031
import com.scrivenvar.spelling.api.SpellChecker;
3132
3233
import java.util.List;
33
import java.util.function.BiConsumer;
3434
3535
/**
...
7070
  @Override
7171
  public void proofread(
72
      final String text, final BiConsumer<Integer, Integer> ignored ) {
72
      final String text, final SpellCheckListener ignored ) {
7373
  }
7474
}
M src/main/java/com/scrivenvar/spelling/impl/SymSpellSpeller.java
2828
package com.scrivenvar.spelling.impl;
2929
30
import com.scrivenvar.spelling.api.SpellCheckListener;
3031
import com.scrivenvar.spelling.api.SpellChecker;
3132
import io.gitlab.rxp90.jsymspell.SuggestItem;
3233
import io.gitlab.rxp90.jsymspell.SymSpell;
3334
import io.gitlab.rxp90.jsymspell.SymSpellBuilder;
3435
3536
import java.text.BreakIterator;
3637
import java.util.ArrayList;
3738
import java.util.Collection;
3839
import java.util.List;
39
import java.util.function.BiConsumer;
4040
4141
import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity;
...
102102
  @Override
103103
  public void proofread(
104
      final String text, final BiConsumer<Integer, Integer> consumer ) {
104
      final String text, final SpellCheckListener consumer ) {
105105
    assert text != null;
106106
    assert consumer != null;
M src/main/resources/com/scrivenvar/messages.properties
7272
Preferences.r.directory=Working Directory
7373
Preferences.r.directory.desc=Value assigned to $application.r.working.directory$ and usable in the startup script.
74
Preferences.r.delimiter.began=Delimiter Prefix
75
Preferences.r.delimiter.began.desc=Prefix of expression that wraps inserted definitions.
76
Preferences.r.delimiter.ended=Delimiter Suffix
77
Preferences.r.delimiter.ended.desc=Suffix of expression that wraps inserted definitions.
7478
7579
Preferences.images=Images